递归全排列
2016-05-04 18:49
176 查看
参考http://www.cnblogs.com/bakari/archive/2012/08/02/2620826.html
http://blog.csdn.net/niuox/article/details/7663146
1.试想,我们只有两个数字:12.要对它进行全排列,第一种方式就是12本身,第二种,将12交换,变为21即可。这提示了我们一种交换的思路。
2.但这概括的并不全面。试想,我们要对123进行全排列。我们可以采用将1固定,“23”进行全排列,将“2”固定,对“13”进行全排列。将“3”固定,对“12”进行全排列。
这其实就是首部为”1“,然后是“2”,然后是“3”,不就是第二位后边的数依次和第一位进行交换么?这是典型的递归的思路。
3.但是,这样也不全面,我们每次交换要将排列恢复成为原始的“123”,因为这个算法求排列的时候,前后并没有依赖性,其参考物只有“123”这个原始的第一个排列。否则,如果我们不恢复的话,就会出现,虽然数量与正确解法相同,但是会有重复的排列的现象。
一、 递归版本
1、算法简述
简单地说:就是第一个数分别以后面的数进行交换
E.g:E = (a , b , c),则 prem(E)= a.perm(b,c)+ b.perm(a,c)+ c.perm(a,b)
然后a.perm(b,c)= ab.perm(c)+ ac.perm(b)= abc + acb.依次递归进行
3、见图知晓
![](http://pic002.cnblogs.com/images/2012/431521/2012080223330272.png)
不过这样存在一点小小的缺陷:两个相同的数也进行了交换,见下图:
![](http://pic002.cnblogs.com/images/2012/431521/2012080223334360.png)
明显,这绝对不符合要求:
4、代码改进
去掉重复符号的全排列:在交换之前可以先判断两个符号是否相同,不相同才交换,这个时候需要一个判断符号是否相同的函数。
所以,改进的代码如下:
OK,见图知情况
![](http://pic002.cnblogs.com/images/2012/431521/2012080223395958.png)
二、 非递归版本
1、算法简述
要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。
如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。
如果达到这个数的最大,比如1234-à4321,这个时候就结束整个循环。
如果输入是一个非最小数,如1324,则将它转换为最小数,如1234,再进行排序。排序算法用快排,可以自己写一个,如果快排不会的话,就先看会再来接着看,或者自己想一个靠谱的算法,也可以直接用VC库中的qsort(s , n , sizeof(s[0]) , cmp);各参数是什么意思就自己在下面多花点时间吧。
OK,下面看代码分析
2、代码分析
上面涉及到几个函数
说一下找与替换数交换的数的函数
!!!这里要说明:从后往前找的第一个比替换数大的数一定就是要找的最小数,Why,这个慢慢品味,我做的时候也遇到一定的困难,自己去做,不经历就不会轻易铭记。
其他函数简直就是小case了。祝君成功!
3、见图知晓
![](http://pic002.cnblogs.com/images/2012/431521/2012080223435978.png)
![](http://pic002.cnblogs.com/images/2012/431521/2012080223442392.png)
三、非递归还有一种方法
描述:和上一种不同的是:这种算法比较笨,但很好理解,不用按照上一种那么严格从小到大进行排列输出。
首先先将最后一个数从右往左依次交换输出,然后判断个数是否为基数,交换离该数最远端的两个数,再把第一个数从左往右交换输出,交换远端的两个数,如此进行循环就能排列完全部的数。这说得可能比较抽象,看一个例子:
E.g: 1 2 3 4
第一次:(从右往左):1 2 4 3 --- 1 2 4 3 --- 1 4 2 3 --- 4 1 2 3 把最后一个数依次往前移
交换:2 和 3 ---> 4 1 3 2
第二次:(从左往右):4 1 3 2 --- 1 4 3 2 --- 1 3 4 2 --- 1 3 2 4 把第一个数依次往后移
交换:1 和 3 ----> 3 1 2 4 重复第一次,知道把所有数输出为止
看代码:
四、 总结
至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:
1.全排列就是从第一个数字起每个数分别与它后面的数字交换。
2.去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
3.全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。
http://blog.csdn.net/niuox/article/details/7663146
1.试想,我们只有两个数字:12.要对它进行全排列,第一种方式就是12本身,第二种,将12交换,变为21即可。这提示了我们一种交换的思路。
2.但这概括的并不全面。试想,我们要对123进行全排列。我们可以采用将1固定,“23”进行全排列,将“2”固定,对“13”进行全排列。将“3”固定,对“12”进行全排列。
这其实就是首部为”1“,然后是“2”,然后是“3”,不就是第二位后边的数依次和第一位进行交换么?这是典型的递归的思路。
3.但是,这样也不全面,我们每次交换要将排列恢复成为原始的“123”,因为这个算法求排列的时候,前后并没有依赖性,其参考物只有“123”这个原始的第一个排列。否则,如果我们不恢复的话,就会出现,虽然数量与正确解法相同,但是会有重复的排列的现象。
一、 递归版本
1、算法简述
简单地说:就是第一个数分别以后面的数进行交换
E.g:E = (a , b , c),则 prem(E)= a.perm(b,c)+ b.perm(a,c)+ c.perm(a,b)
然后a.perm(b,c)= ab.perm(c)+ ac.perm(b)= abc + acb.依次递归进行
Perm( char *pszStr , int k , int m ) 3 { 4 if (k == m) 5 { 6 static int s_i = 1; 7 cout<<” 第 ”<<s_i ++<<” 个排列 ”<<pszStr<<endl; 8 } 9 else 10 { 11 for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列 12 { 13 Swap(pszStr + k, pszStr + i); 14 Perm(pszStr, k + 1, m); 15 Swap(pszStr + k, pszStr + i); 16 } 17 } 18 }
3、见图知晓
![](http://pic002.cnblogs.com/images/2012/431521/2012080223330272.png)
不过这样存在一点小小的缺陷:两个相同的数也进行了交换,见下图:
![](http://pic002.cnblogs.com/images/2012/431521/2012080223334360.png)
明显,这绝对不符合要求:
4、代码改进
去掉重复符号的全排列:在交换之前可以先判断两个符号是否相同,不相同才交换,这个时候需要一个判断符号是否相同的函数。
bool IsSwap(char *pszStr, int nBegin, int nEnd) { for (int i = nBegin; i < nEnd; i++) if (pszStr[i] == pszStr[nEnd]) return false; return true; }
所以,改进的代码如下:
1 Perm(char *pszStr, int k, int m) 2 { 3 if (k == m) 4 { 5 Static int s_i = 1; 6 cout<<” 第 ”<<s_i ++<<” 个排列 ”<<pszStr<<endl; 7 } 8 else 9 { 10 for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列 11 { 12 if (IsSwap(pszStr, k, i)) //添加的判断语句,判断是否相等 13 { 14 Swap(pszStr + k, pszStr + i); 15 Perm(pszStr, k + 1, m); 16 Swap(pszStr + k, pszStr + i); 17 } 18 } 19 } 20 }
OK,见图知情况
![](http://pic002.cnblogs.com/images/2012/431521/2012080223395958.png)
二、 非递归版本
1、算法简述
要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。
如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。
如果达到这个数的最大,比如1234-à4321,这个时候就结束整个循环。
如果输入是一个非最小数,如1324,则将它转换为最小数,如1234,再进行排序。排序算法用快排,可以自己写一个,如果快排不会的话,就先看会再来接着看,或者自己想一个靠谱的算法,也可以直接用VC库中的qsort(s , n , sizeof(s[0]) , cmp);各参数是什么意思就自己在下面多花点时间吧。
OK,下面看代码分析
2、代码分析
1 Prem( char *s ) //全排列函数 2 { 3 char *pEnd = s + strlen(s) - 1; 4 char *p = pEnd; //p代表替换点 5 //q代表替换点的下一个数 ,pMax 代表替换点后比替换点大的最小数 6 char *q = new char,*pMax = new char; //注意初始化!!! 7 while (p != s) //p == s 就结束循环 8 { 9 q = p; 10 p--; 11 if (*p < *q) 12 { 13 pMax = FindMaxForOne(p,pEnd); //找与替换点交换的点 14 Swap(p,pMax); //交换 15 Reverse(q,pEnd); //将替换点后所有数进行反转 16 Print(s); //输出 17 p = pEnd; //将替换点置最后一个点,开始下一轮循环 18 } 19 if (s == p) break; //结束条件 20 } 21 }
上面涉及到几个函数
说一下找与替换数交换的数的函数
1 char* FindMaxForOne(char *p,char *q) 2 { 3 char *p1 = p; 4 char *p2 = q; 5 while (*p2 <= *p1) p2--; 6 return p2; 7 }
!!!这里要说明:从后往前找的第一个比替换数大的数一定就是要找的最小数,Why,这个慢慢品味,我做的时候也遇到一定的困难,自己去做,不经历就不会轻易铭记。
其他函数简直就是小case了。祝君成功!
3、见图知晓
![](http://pic002.cnblogs.com/images/2012/431521/2012080223435978.png)
![](http://pic002.cnblogs.com/images/2012/431521/2012080223442392.png)
三、非递归还有一种方法
描述:和上一种不同的是:这种算法比较笨,但很好理解,不用按照上一种那么严格从小到大进行排列输出。
首先先将最后一个数从右往左依次交换输出,然后判断个数是否为基数,交换离该数最远端的两个数,再把第一个数从左往右交换输出,交换远端的两个数,如此进行循环就能排列完全部的数。这说得可能比较抽象,看一个例子:
E.g: 1 2 3 4
第一次:(从右往左):1 2 4 3 --- 1 2 4 3 --- 1 4 2 3 --- 4 1 2 3 把最后一个数依次往前移
交换:2 和 3 ---> 4 1 3 2
第二次:(从左往右):4 1 3 2 --- 1 4 3 2 --- 1 3 4 2 --- 1 3 2 4 把第一个数依次往后移
交换:1 和 3 ----> 3 1 2 4 重复第一次,知道把所有数输出为止
看代码:
1 /************************************************************************ 2 * Author: bakari 3 * Date: 2011.5.7 4 /************************************************************************/ 5 int n; 6 void swap(int *a,int *b); //交换函数 7 void print(int a[]); //打印交换后的每一组数 8 int jfc(); //求阶乘函数 9 int jmp(int n); //跳转函数 10 void sort(int a[]); //全排列函数 11 12 int main(){ 13 while(cin>>n) 14 { 15 while(n<=0) 16 { 17 cout<<"输入有误!请重新输入: "; 18 cin>>n; 19 } 20 int *a=new int ; 21 for(int i=0;i<n;i++) 22 a[i]=i+1; 23 sort(a); 24 delete []a; 25 } 26 system("pause"); 27 return 0; 28 } 29 30 void swap(int *a,int *b) 31 { 32 int t=*a; 33 *a=*b; 34 *b=t; 35 } 36 void print(int a[]) 37 { 38 for(int i=0;i<n;i++) 39 cout<<a[i]<<' '; 40 cout<<endl; 41 42 } 43 int jfc() 44 { 45 int s=1; 46 for(int i=1;i<=n;i++) 47 s*=i; 48 return s; 49 } 50 int jmp(int n) 51 { 52 if(n>jfc()) 53 return 0; 54 } 55 void sort(int a[]) 56 { 57 int m=1,count=0; //m统计全排列的个数,count统计行数 58 int *p1,*p2; 59 for(p1=a+n-1,p2=a+n-2;p1>=a+1,p2>=a;p1--,p2--) 60 { 61 print(a); 62 swap(p1,p2); 63 m++; 64 } 65 count++; 66 while(m<=jfc()){ 67 if(count%2) 68 { print(a); 69 swap(&a[n-1],&a[n-2]); 70 m++; 71 if(!jmp(m)) 72 break; 73 for(p1=a,p2=a+1;p1<=a+n-2,p2<=a+n-1;p1++,p2++) 74 { 75 print(a); 76 swap(p1,p2); 77 m++; 78 } 79 count++; 80 } 81 else 82 { 83 print(a); 84 swap(&a[0],&a[1]); 85 m++; 86 if(!jmp(m)) 87 break; 88 for(p1=a+n-1,p2=a+n-2;p1>=a+1,p2>=a;p1--,p2--) 89 { 90 print(a); 91 swap(p1,p2); 92 m++; 93 } 94 count++; 95 } 96 97 } 98 cout<<"共有"<<m-1<<"种排列"<<endl; 99 }
四、 总结
至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:
1.全排列就是从第一个数字起每个数分别与它后面的数字交换。
2.去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
3.全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。
相关文章推荐
- Android数据库安全解决方案,使用SQLCipher进行加解密
- 使用mktime 函数取得一个日期的时间戳
- Mac 下安装 Python-OpenCV Python-OpenCV 处理图像(一):基本操作
- 设计模式学习网站
- 第一次冲刺阶段对各小组意见的回复
- webpack入门
- opengl SwapBuffers的等待,虚伪的FPS
- MVC框架显示层——Velocity技术
- java字符串subString的实现
- Oracle 故障处理总结
- ARMs3c2440开发板挂接NFS服务
- bzoj2243 [SDOI2011]染色
- Linux下文件编码转换
- SharedPreferences
- [Android] AIDL的使用情况和实例介绍
- 实现指定网段通过指定路由传输数据
- 《java入门第一季》之面向对象面试题(面向对象都做了哪些事情)
- 《java入门第一季》之面向对象面试题(面向对象都做了哪些事情)
- 分布式配置管理平台 Disconf
- 搭建ORACLE高可用 高性能 高扩展的 MMM_APE 架构