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

举报
悦来客栈的老板 发表于 2020/12/28 23:46:15 2020/12/28
【摘要】 算法思想  假设总共有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 ...

算法思想

 假设总共有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
//此算法的字符串里面不能有重复的字符


  
  1. #include <stdio.h>
  2. #include <assert.h>
  3. #define swap(a,b) {int t; t=a; a=b; b=t;}
  4. void permutation(char str[], char begin[])
  5. {
  6. char *ch;
  7. assert(str && begin);
  8. if (*begin == '\0')
  9. {
  10. printf("%s\n",str);
  11. }
  12. else
  13. {
  14. for (ch = begin; *ch != '\0'; ch++)
  15. {
  16. swap(*begin,*ch);//遍历字符串,把第i个和第一个交换
  17. permutation(str, begin+1);
  18. swap(*begin,*ch);//将第i个放回原处
  19. }
  20. }
  21. }
  22. int main()
  23. {
  24. char str[] = "abcd";
  25. permutation(str,str);
  26. return 0;
  27. }


 

另外一种写法:


  
  1. #include <stdio.h>
  2. #include <assert.h>
  3. #include <string.h>
  4. #define swap(a,b) {char t; t = a; a = b; b = t;}
  5. void Permutation(char *str, int k, int m)
  6. {
  7. int i;
  8. assert(str);
  9. if (k == m)
  10. {
  11. static int num = 1;
  12. printf("第%d个排列\t%s\n",num,str);
  13. num++;
  14. }
  15. else
  16. {
  17. for (i=k; i<=m; i++)
  18. {
  19. swap(*(str+k),*(str+i)); //把第i个和第一个(此时是k)交换
  20. Permutation(str, k+1, m);
  21. swap(*(str+k),*(str+i)); //将第i个放回原处
  22. }
  23. }
  24. }
  25. int main()
  26. {
  27. char str[] = "abc";
  28. Permutation(str, 0, strlen(str)-1);
  29. return 0;
  30. }


如何去掉有重复的字符?

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

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


  
  1. #include <stdio.h>
  2. #include <assert.h>
  3. #define swap(a,b) {char t;t = a; a = b; b = t;}
  4. int Isswap(char *begin, char *end)
  5. {
  6. char *p;
  7. for (p = begin; p<end; p++)
  8. {
  9. if (*p == *end)//如果后面有相等的字符,则不予交换
  10. {
  11. return 0;
  12. }
  13. }
  14. return 1;
  15. }
  16. void permutation(char *str, char *begin)
  17. {
  18. char *ch;
  19. assert(str && begin);
  20. if(*begin == '\0')
  21. {
  22. static int num = 1; //局部静态变量,用来统计全排列的个数
  23. printf("第%d个排列\t%s\n",num,str);
  24. num++;
  25. }
  26. else
  27. {
  28. for (ch = begin; *ch != '\0'; ch++)
  29. {
  30. if (Isswap(begin, ch))
  31. {
  32. swap(*begin, *ch);
  33. permutation(str,begin+1);
  34. swap(*begin, *ch);
  35. }
  36. }
  37. }
  38. }
  39. int main(void)
  40. {
  41. char str[] = "abbac";
  42. permutation(str , str);
  43. return 0;
  44. }


 

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

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

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

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

 


  
  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<assert.h>
  4. #include <stdlib.h>
  5. #define swap(a,b) {char t;t = a; a = b; b = t;}
  6. //反转区间
  7. void Reverse(char* begin , char* end)
  8. {
  9. while(begin < end)
  10. {
  11. swap(*begin , *end);
  12. begin++;
  13. end--;
  14. }
  15. }
  16. //下一个排列
  17. int Next_permutation(char str[])
  18. {
  19. char *p , *q , *pFind;
  20. char *end = str + strlen(str) - 1;
  21. assert(str);
  22. if(str == end)
  23. return 0;
  24. p = end;
  25. while(p != str)
  26. {
  27. q = p;
  28. p--;
  29. if(*p < *q) //找降序的相邻2数,前一个数即替换数
  30. {
  31. //从后向前找比替换点大的第一个数
  32. pFind = end;
  33. while(*pFind <= *p)
  34. {
  35. --pFind;
  36. }
  37. swap(*p , *pFind);
  38. //替换点后的数全部反转
  39. Reverse(q , end);
  40. return 1;
  41. }
  42. }
  43. Reverse(str , end); //如果没有下一个排列,全部反转后返回false
  44. return 0;
  45. }
  46. int cmp(const void *a,const void *b)
  47. {
  48. return (int) (*(char *)a - *(char *)b);
  49. }
  50. int main(void)
  51. {
  52. char str[] = "9876543210";
  53. int num = 1;
  54. qsort(str , strlen(str),sizeof(char),cmp);
  55. do
  56. {
  57. printf("第%d个排列\t%s\n",num,str);
  58. num++;
  59. }while(Next_permutation(str));
  60. return 0;
  61. }


 


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

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

 

 

文章来源: blog.csdn.net,作者:悦来客栈的老板,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/qq523176585/article/details/13772531

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。