每日一算法:全排列的递归算法与非递归算法

来源:互联网 发布:polyfit函数的算法 编辑:程序博客网 时间:2024/06/06 01:49

算法思想

 假设总共有n个元素,其核心是:将每个元素放到余下n-1个元素组成的队列最前方,然后对剩余元素进行全排列,依次递归下去。
比如:1 2 3
首先将1放到最前方(跟第1个元素交换),然后排列余下的2 3,然后将1放回本来位置
结果 1 2 3;1 3 2
 
其次将2放到最前方(跟第1个元素交换),然后排列余下的1 3,然后将2放回原处
结果 2 1 3; 2 3 1
。。。。。
 
如果是4个元素,就将元素依次放到第一个元素的位置,后面的排序类似前面的3元素排序。

也可以等价于下面的思路
我们有一字符串为abc,对应的全排列有6种:abc、acb、bca、bac、cab和cba。它可以由下述规则得到:
Abc的全排列可以由下述规律得到:
a + 全排列(bc)
b + 全排列(ac)
c + 全排列(ab)
而各个子串全排列可以看作总串的一个相同但规模较小的问题,即用递归解决。

 

//因此我们比较容易得出算法1
//此算法的字符串里面不能有重复的字符

#include <stdio.h>#include <assert.h>#define swap(a,b) {int t; t=a; a=b; b=t;}void permutation(char str[], char begin[]){char *ch;assert(str && begin);if (*begin == '\0'){printf("%s\n",str);}else{for (ch = begin; *ch != '\0'; ch++){swap(*begin,*ch);//遍历字符串,把第i个和第一个交换permutation(str, begin+1);swap(*begin,*ch);//将第i个放回原处}}}int main(){char str[] = "abcd";permutation(str,str);return 0;}


 

另外一种写法:

#include <stdio.h>#include <assert.h>#include <string.h>#define swap(a,b) {char t; t = a; a = b; b = t;}void Permutation(char *str, int k, int m){int i;assert(str);if (k == m){static int num = 1;printf("第%d个排列\t%s\n",num,str);  num++;}else{for (i=k; i<=m; i++){swap(*(str+k),*(str+i));   //把第i个和第一个(此时是k)交换 Permutation(str, k+1, m);swap(*(str+k),*(str+i));   //将第i个放回原处 }}}int main(){char str[] = "abc";Permutation(str, 0, strlen(str)-1);return 0;}


如何去掉有重复的字符?

由于全排列就是从第一个数字起每个数分别与它后面的数字交换。
我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。
如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,
交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。

换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,
所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。
这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。

#include <stdio.h>#include <assert.h>#define swap(a,b) {char t;t = a; a = b; b = t;}int Isswap(char *begin, char *end){char *p;for (p = begin; p<end; p++){if (*p == *end)//如果后面有相等的字符,则不予交换{return 0;}}return 1;}void permutation(char *str, char *begin){char *ch;assert(str && begin);if(*begin == '\0')      {          static int num = 1;  //局部静态变量,用来统计全排列的个数          printf("第%d个排列\t%s\n",num,str); num++;    } else{for (ch = begin; *ch != '\0'; ch++){if (Isswap(begin, ch)){swap(*begin, *ch);permutation(str,begin+1);swap(*begin, *ch);}}}}int main(void){char str[] = "abbac";permutation(str , str);return 0;}


 

下面来考虑非递归算法
要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。
如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。

如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字(为什么这第一双相邻的递增数字,因为这在找下一个比它大的数),
"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,
再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",
然后再将替换点后的字符串"6220"颠倒即得到"950226"。

如果达到这个数的最大,比如1234->4321,这个时候就结束整个循环。

如果输入是一个非最小数,如1324,则将它转换为最小数,如1234,再进行排序。

 

#include<stdio.h>#include<string.h>#include<assert.h>#include <stdlib.h>#define swap(a,b) {char t;t = a; a = b; b = t;}//反转区间void Reverse(char* begin , char* end){while(begin < end){swap(*begin , *end);begin++;end--;}}//下一个排列int Next_permutation(char str[]){char *p , *q , *pFind;char *end = str + strlen(str) - 1;assert(str);if(str == end)return 0;p = end;while(p != str){q = p;p--;if(*p < *q)  //找降序的相邻2数,前一个数即替换数  { //从后向前找比替换点大的第一个数pFind = end;while(*pFind <= *p){--pFind;}swap(*p , *pFind);//替换点后的数全部反转Reverse(q , end);return 1;}}Reverse(str , end);   //如果没有下一个排列,全部反转后返回false   return 0;}int cmp(const void *a,const void *b){return (int) (*(char *)a - *(char *)b);}int main(void){char str[] = "9876543210";int num = 1;qsort(str , strlen(str),sizeof(char),cmp);do{printf("第%d个排列\t%s\n",num,str); num++;}while(Next_permutation(str));return 0;}


 


至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:
1、全排列就是从第一个数字起每个数分别与它后面的数字交换。
2、去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
3、全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。

代码原文地址:http://blog.csdn.net/morewindows/article/details/7370155

 

 

原创粉丝点击