由于老师提出了全排列问题。我心血来潮,将组合也弄了一下。 现在写篇日志记录一下。

全排列

目前我所知道的全排列算法有三种,下面一一介绍:

(1)分治算法:这个算法利用了分而治之的思想。我们先从2个数开始,比如说4,5,他们的全排列只有两个45和54。如果在前面加个3,那么全排列就是345,354,也就是3(54),括号表示里面的数的全排列。当然还有4(35),5(34)...写到这里,各位看官应该已经看出点门道了吧。三个数的全排列,可以分为三次计算,第一次计算3和(45)的全排列,第二次计算4和(35)的全排列.....也就是说,将序列第一个元素分别与它以及其后的每一个元素代换,得到三个序列,然后对这些序列的除首元素外的子序列进行全排列。思想其实还是挺简单的:

代码实现如下:

Code:
  1. //input 8 12.61s consumed
  2. //input 8 11.72s consumed remove '-' in the printed array
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <stdlib.h>
  6. #define LENGTH 27
  7. int n=0;
  8. void permute(int[],int,int);
  9. void swapint(int &a,int &b);
  10. void printIntArray (int[],int);
  11. //Function Implementation
  12. void swapint(int &a,int &b){
  13. int temp;
  14. temp = a;
  15. a = b;
  16. b = temp;
  17. }
  18. void printIntArray(int target[],int length){
  19. int i;
  20. for (i=0;i<length;i++) printf ("%d",target[i]);
  21. printf ("/n");
  22. }
  23. void permute(int target[],int begin,int end){
  24. if (begin==end) {
  25. printIntArray(target,end+1);
  26. n++;
  27. return;
  28. }
  29. int i;
  30. for (i=begin;i<=end;i++){
  31. swapint(target[begin],target[i]);
  32. permute(target,begin+1,end);
  33. swapint(target[begin],target[i]);
  34. }
  35. }
  36. //test Functions
  37. void testPermute(){
  38. int len;
  39. scanf ("%d",&len);
  40. int *testcase =(int *)malloc(len*sizeof(int));
  41. int i;
  42. for (i=0;i<len;i++) testcase[i]=i+1;
  43. permute(testcase,0,len-1);
  44. }
  45. //Main Function
  46. void main(){
  47. testPermute();
  48. printf ("n=%d",n);
  49. }

需要注意的是交换了元素,然后进行递归对子序列进行全排列之后,需要将元素的位置换回来。这是为了要还原现场,也就是说递归函数permute的形参target数组在内存中只有一个,递归调用的时候,入栈的只是一个数组指针,递归对子序列进行全排列之后,必定会对原序列造成一定的乱序。因此每次递归之后,都需要还原现场。

当然还有一个方法是将数组放在一个结构体中,而且还不能使用指针哦。参数调用的时候也不能使用引用。这样递归的时候,内存中才会生成新的结构体,当然也有新的数组了。就不会影响其他的操作,不过可想而知,这样写多浪费内存空间,当然也浪费时间。

(2)字典序列算法

字典序列算法是一种非递归算法。而它正是STL中Next_permutation的实现算法。我们来看看他的思路吧:

它的整体思想是让排列成为可递推的数列,也就是说从前一状态的排列,可以推出一种新的状态,直到最终状态。比如说,最初状态是12345,最终状态是54321。其实我觉得这跟我们手动做全排列是一样的。首先是12345,然后12354,然后12435,12453....逐渐地从后往前递增。

看看算法描述:

首先,将待排序列变成有序(升序)序列。

然后,从后向前寻找,找到相邻的两个元素,Ti<Tj,(j=i+1)。如果没有找到,则说明整个序列已经是降序排列了,也就是说到达最终状态54321了。此时,全排列结束。

接着,如果没有结束,从后向前找到第一个元素Tk,使得Tk>Ti(很多时候k=j),找到它,交换Ti跟Tk,并且将Tj到Tn(Tn是最后一个元素)的子序列进行倒置操作。输出此序列。并回到第二步继续寻找ij.

算法是很清晰的。看看代码:

Code:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. //function definition
  4. void Swap(int &a,int &b);
  5. void Reverse (int[],int,int);
  6. void printArray (int[],int);
  7. int nextPermutation(int[],int);
  8. void Swap(int &a,int &b){
  9. int tmp;
  10. tmp=a;
  11. a=b;
  12. b=tmp;
  13. }
  14. void Reverse(int target[],int begin,int end){
  15. while (begin<end){
  16. Swap(target[begin],target[end]);
  17. begin++;
  18. end--;
  19. }
  20. }
  21. //function implementation
  22. int nextPermutation(int target[],int end){
  23. int i,j;
  24. for (i=end-1;i>=0;i--)
  25. if (target[i]<target[i+1]) break;
  26. if (i<0) return 0; //means the permutation is over
  27. j=i+1;
  28. int k;
  29. for (k=end;target[k]<=target[i];k--);
  30. Swap(target[i],target[k]);
  31. Reverse(target,j,end);
  32. return 1;
  33. }
  34. void printArray (int target[],int length){
  35. int i;
  36. for (i=0;i<length;i++) printf ("%d-",target[i]);
  37. printf ("/n");
  38. }
  39. //test function
  40. void testPermute(){
  41. printf ("input a number:");
  42. int n;
  43. scanf ("%d",&n);
  44. int i;
  45. int * testcase = (int*)malloc(n*sizeof(int));
  46. for (i=0;i<n;i++) testcase[i]=i+1;
  47. printArray (testcase,n);
  48. while (nextPermutation(testcase,n-1)){
  49. printArray (testcase,n);
  50. }
  51. }
  52. //main function
  53. void main(){
  54. testPermute();
  55. }

代码完全按照算法来做,大家应该不难看懂。我就不废话了。

(3)深搜回溯算法

深度搜索,判重,回溯,这个算法应该算是一个基础了。其中八皇后问题是它的经典应用。说实话感觉它就像个万金油一样,在啥地方都能使,但是就是不一定好使就对了。不过还是要实现一下。

其实细想一下,全排列跟八皇后问题是一样一样的。也是穷举一个数列,只是八皇后对数列的条件跟严格,也即是判重函数不同而已。没有区别的。看看代码:

Code:
  1. //n=8 Time Consumed 11.78s
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. //function definition
  5. void printArray (const int[],int);
  6. bool isExist(const target[],int);
  7. void permute(int[],const int,int);
  8. //function implementation
  9. void printArray(const int target[],int n){
  10. int i;
  11. for (i=0;i<n;i++)
  12. printf ("%d",target[i]);
  13. printf ("/n");
  14. }
  15. bool isExist(const int target[],int pos){
  16. if (pos==0) return false;
  17. int i;
  18. for (i=0;i<pos;i++)
  19. if (target[i]==target[pos]) return true;
  20. return false;
  21. }
  22. void permute (int target[],const int n,int i){
  23. if (i==n) {
  24. printArray(target,n);
  25. return;
  26. }
  27. for (target[i]=1;target[i]<=n;target[i]++)
  28. if (isExist(target,i) == false) permute(target,n,i+1);
  29. //target[i]=1;
  30. return;
  31. }
  32. //test functions
  33. void testPermutation(){
  34. int n;
  35. printf ("Input a Number:");
  36. scanf ("%d",&n);
  37. int * testcase=(int*)malloc(n*sizeof(int));
  38. int i;
  39. for (i=0;i<n;i++) testcase[i]=1;
  40. permute(testcase,n,0);
  41. }
  42. //main function
  43. void main(){
  44. testPermutation();
  45. }

注意,我在递归函数permute中,回溯甚至没有将当时位置上的数消掉。看被注释掉的target[i]=1。而是直接在for循环中添加初始语句target[i]=1。这就表示说只要进行深一步搜索,都先将下一位置的数初始化掉。这样就不用在回溯时对当前位置的错误值进行处理了。更加简化了。

再看看非递归版的,也就是使用堆栈的形式:

Code:
  1. // when n=8 time consumed 11.77s
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. //function definition
  5. void permute(int [],int);
  6. void printArray(const int[],const int);
  7. bool isExist(const int[],const int);
  8. //function implementation
  9. void printArray(const int target[],const int n){
  10. int i;
  11. for (i=0;i<n;i++)
  12. printf ("%d",target[i]);
  13. printf ("/n");
  14. }
  15. bool isExist(const int target[],int pos){
  16. if (pos==0) return false;
  17. int i;
  18. for (i=0;i<pos;i++)
  19. if (target[i]==target[pos]) return true;
  20. return false;
  21. }
  22. void permute(int stack[],int n){
  23. int sp=0;   //stack pointer
  24. while (sp>=0) {
  25. stack[sp]++;
  26. while (stack[sp]<=n && isExist(stack,sp)==true ) stack[sp]++;
  27. if (stack[sp]<=n) {
  28. //when stack is full,output the result
  29. //and then backtrack
  30. if (sp==n-1) {
  31. printArray(stack,n);
  32. sp--;
  33. }
  34. else {
  35. //push stack, which means search foward
  36. sp++;
  37. stack[sp]=0;
  38. }
  39. }
  40. else {
  41. //backtrack
  42. sp--;
  43. }
  44. }
  45. return;
  46. }
  47. //test function
  48. void testPermutation(){
  49. int n;
  50. printf ("Input a Number:");
  51. scanf ("%d",&n);
  52. int i;
  53. int * testcase=(int*)malloc(n*sizeof(int));
  54. for (i=0;i<n;i++) testcase[i]=0;
  55. permute(testcase,n);
  56. }
  57. //main function
  58. void main(){
  59. testPermutation();
  60. }

非递归形式算法思想是一样的,不过就是需要自己来维护一个堆栈。注意在循环中,需要首先对堆栈元素进行自加操作,不然的话,回溯之后,就不会改变堆栈值,而使程序陷入死循环。不过当然,你要使用do while来代替这种写法,那当然也是没有问题的。

我只是想说,只要是递归形式的算法,都可以用堆栈来实现非递归的形式。大家不妨来探讨一下这个一一对应的转换的具体形式。

组合

关于组合,我有一个比较帅的算法,在这里跟大家分享一下。呵呵。

这是非递归的算法,我感觉跟全排列的字典序列算法思想上比较像。

你看它是怎么实现的:

求n个数中的m个数的组合。

首先,初始化一个n个元素的数组(全部由0,1组成),将前m个初始化为1,后面的为0。这时候就可以输出第一个组合了。为1的元素的下标所对应的数。

算法开始:从前往后找,找到第一个10组合,将其反转成01,然后将其前面的所有1,全部往左边推,即保证其前面的1都在最左边。然后就可以按照这个01序列来输出一个组合结果了。

而如果找不到10组合,就表示说所有情况都输出了,为什么?你想,(以n=5,m=3为例)一开始是11100,最后就是00111,已经没有10组合了。

这种将问题转换为01序列(也就是真假序列)的想法值得我们考虑和借鉴。

最后看看实现代码:

Code:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. int l=0;
  5. //function definition
  6. void composition(const char [],int,int);
  7. void printCompostion(const char[],const bool[],int);
  8. //function implementation
  9. void printCompostion(const char source[],const bool comp[],int n){
  10. int i;
  11. for (i=0;i<n;i++)
  12. if (comp[i]==true) printf ("%c-",source[i]);
  13. printf ("/n");
  14. }
  15. void compostion(const char* source,int n,int m){
  16. bool * comp = (bool*)malloc(n*sizeof(bool));
  17. int i;
  18. for (i=0;i<m;i++) comp[i]=true;
  19. for (i=m;i<n;i++) comp[i]=false;
  20. printCompostion(source,comp,n);
  21. l++;
  22. while(true){
  23. for (i=0;i<n-1;i++)
  24. if (comp[i]==true&&comp[i+1]==false) break;
  25. if (i==n-1) return;  //all the compostion is found out
  26. comp[i]=false;
  27. comp[i+1]=true;
  28. int p=0;
  29. while (p<i){
  30. while (comp[p]==true) p++;
  31. while (comp[i]==false) i--;
  32. if (p<i) {
  33. comp[p]=true;
  34. comp[i]=false;
  35. }
  36. }
  37. printCompostion(source,comp,n);
  38. l++;
  39. }
  40. }
  41. //test function
  42. void testCompostion(){
  43. char* testcase = "abcdefghijklmno";
  44. int n=strlen(testcase);
  45. int m=7;
  46. compostion(testcase,n,m);
  47. }
  48. //main function
  49. void main(){
  50. testCompostion();
  51. printf ("total=%d/n",l);
  52. }

01序列我是用bool类型来实现的。尽可能地少占用内存吧。而在实现将1往左移的步骤时,我是采用了将左边的0跟右边的1相调换的思想。

关于组合大家如果有更好的方法,不妨跟贴讨论哦。

(完)

排列与组合的算法实现相关推荐

  1. 算法之组合数学及其算法篇(一) ----- 排列与组合

    组合数学及其算法篇 前言 排列与组合 无重集的排列与组合 无重集的排列 应用例子 无重集的组合 应用例子 重集的排列和组合 重集的排列 重集的组合 前言 组合数学研究的对象是组态.所谓组态就是指若干个 ...

  2. 回溯算法团灭子集、排列、组合问题

    回溯算法团灭子集.排列.组合问题 一.子集 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: nums = [1,2,3] ...

  3. 【离散数学中的数据结构与算法】六 排列与组合二

    接着上一篇学习:[离散数学中的数据结构与算法]五 排列与组合一 上一篇文章主要学习了可重复选取的可重排列和不可重复选取的排列.他们都是在n个不同的对象中选取. 今天我们俩学习的是,当这个n个对象中有相 ...

  4. java排列和组合算法

    package com.louisgeek.price;/*** 排列和组合算法* @author Administrator**/ public class CaseTest {public sta ...

  5. java 实现组合_用Java实现排列、组合算法

    组合个数的计算公式如下: 那么,计算排列或组合的数量,通过上面的公式就很容易就算出来了,其Java的实现如下: /** * 计算阶乘数,即n! = n * (n-1) * ... * 2 * 1 * ...

  6. 通用的非递归排列和组合算法[附源码]

    动机 Apache Math包中有很多关分布的算法,但是没有找到排列组合相关的算法.索性自己写一个.排列组合可以分两个算法: 组合算法,就是在一个数组中取出m(小于等于数组的长度 n)个对象,有多少中 ...

  7. 多重集合的排列和组合问题

    多重集合的排列和组合问题 标签: permutationn2c扩展 2012-04-17 16:18 5671人阅读 评论(0) 收藏 举报  分类: 算法(12)  版权声明:本文为博主原创文章,未 ...

  8. 从n返回k个元素的所有组合的算法

    我想编写一个函数,该函数采用字母数组作为参数,并选择多个字母. 假设您提供8个字母的数组,并希望从中选择3个字母. 然后您将获得: 8! / ((8 - 3)! * 3!) = 56 返回由3个字母组 ...

  9. 算法组合 优化算法_算法交易简化了风险价值和投资组合优化

    算法组合 优化算法 Photo by Markus Spiske (left) and Jamie Street (right) on Unsplash Markus Spiske (左)和Jamie ...

最新文章

  1. Visual Studio 2010 Express for Windows Phone Key!
  2. 推荐一个Android开源项目-AisenWeiBo
  3. 大二寒假 之 丢失的13天
  4. 深度学习:又一次推动AI梦想(Marr理论、语义鸿沟、视觉神经网络、神经形态学)
  5. 玩转oracle 11g(15):命令学习3
  6. 输出一串小于100的随机数(rand())
  7. python基础30个常用代码-30个Python常用极简代码,拿走就用
  8. 数据分析为何能指导商业决策?
  9. [手把手教你] 用Swoft 搭建微服务(TCP RPC)
  10. 进军杀毒市场!百度杀毒软件2013正式发布
  11. WWDC22 - In App Purchase 更新总结
  12. VHDL实现数码管的动态扫描(可以连接其他的模块)
  13. rstudio安装后如何打开_请问Rstudio安装后无法运行怎么弄?
  14. UDP/TCP实时传输图像
  15. 神经网络学习笔记(一):全连接层的作用是什么?
  16. web.xml学习随笔
  17. 浅谈敏捷思想-08.从产品愿景到用户故事地图
  18. 笔记:Solow 模型整理
  19. JavaScript计算两个日期之间相差的天数
  20. 告别 .com网址时代,Opera浏览器实现用Emoji符号打开网站

热门文章

  1. 瑞星U盘杀毒工具制作! 实验完全正确。
  2. linux 如何删除log文件,linux的删除文件日志命令是什么
  3. 一次简单的SQL注入靶场练习
  4. 从零开始构建PHP版mud游戏(二)
  5. PMBOK工具和技术详解-成本效益分析
  6. 鸿蒙系统能适配麒麟970处理器么,鸿蒙OS手机版即将适配,麒麟970及以下处理器或无缘更新...
  7. IDEA使用lombok
  8. php在pdf文件上写字
  9. WEB网页设计期末作业个人主页——基于HTML+CSS制作个人简介网站
  10. 二叉树的最近公共祖先 (BSF和普通)