字符串的全排列【递归算法训练】

  前几天,师兄轻描淡写的出了一道题,对于一个给定的字符串,输出它的全排列结果,例如,输入ab,则程序需要输出ab,ba[结果数为2*1=2]。额外的要求是对于字符串中的重复字符,程序要能识别出来并进行去重处理,例如,输入aab,则程序需要输出baa,aba,aab[结果数为3,而不是3*2=6]。

  这里我用了两种思路实现了这道题,不过都属于递归,第一种思路是我自己想的,只能输出全排列,在去重处理上尚需完善[例如,输入aab,程序可以得到正确结果;但如果输入aaba,程序就得不到完全去重的结果了,即重复字符只能连续出现,否则程序就会出现bug,究其原因还是递归时记不住以前处理的结果,如果记录这些结果,则空间开销太大,而且每次都要检查是否与以前的结果相同,付出的时间代价也很大] 。

  第二种思路是另一位师兄指点我的,在他提出的方案基础上,我做了完善,不但可以输出全排列,而且可以达到去重要求,算是一个完整的算法。

  第一种思路:求字符串a{n}的全排列,可以分解为求a{n - 1}的全排列,对于a{n - 1}的每一个全排列结果,只要将a[n]插入到a{n - 1}序列的n个位置中,即可得到a{n}的全排列结果。例如:求abc的全排列,可以先求ab的全排列,再将c插入到第0,1,2位即可得到abc的全排列,而对于ab的全排列,可以先求a的全排列,再将b插入到第0,1位即可得到ab全排列结果,因为a是单个字符,它的全排列结果只有一个:a,所以递归开始返回,层层向上,得到abc的全排列的解。总结:这是基于插入操作的算法!

  第二种思路:求字符串a{n}的全排列,首先将a[0]和a[n]调换,然后求前面长度为n - 1的字符串的全排列,完成后,将a{n}序列恢复原样,再将a[1]和a[n]调换,以此类推,直到a[n]和a[n]调换,去求前面长度为n - 1的字符串的全排列,至此所有的结果均已得到。例如:求abc的全排列,可以先将a和c调换并固定a,即cba,此时求cb的全排列,将c和b调换并固定c,即bca,由于b是一个单独的字符串,此次递归到底层,输出一个结果bca,返回,回归原样为cba,此时下一个需要处理的值是b,即b和b调换【即a[i]和a[n]调换,但此时i恰好等于n】,得到cba,由于c是单独的字符串,递归到底层,输出一个结果cba,返回,回归原样为cba,因为i已经等于n,则继续返回,回归原样abc,此时a已经处理完毕,再将b放在最末尾固定,进行处理,以此类推,得到所有全排列结果。这个思路有一个好处,即付出少量的空间代价即可达到去重处理的要求,对于重复的字符,程序只要发现这个字符曾经在i位置固定过,就不再进行此轮处理,因为对于任意字符m,在i位置固定过,则可知m在i位置的前提下整个字符串的全排列结果已经得到,那么相同的字符再一次固定在相同的位置,处理的全排列必然导致结果重复。总结:这是基于调换操作的算法!

  第一种思路的源代码【不完整,去重还有bug】:

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 
 5 int count = 0;//结果数目
 6 
 7 void insert(char *a, int nowlength, char b)//将字符b插入到长度为nowlength的字符串中的0,1,2,...,nowlength位置
 8 {
 9     int i;
10     int j;
11     char temp[10];
12     int step;
13     for(i = 0; i < nowlength; i ++)
14     {
15         temp[i] = a[i];
16     }//记住a{nowlength}的原样,b每一轮插入操作得到结果后,在进行下一轮操作时a要恢复原样
17     temp[i] = '\0';
18     for(j = 0; j <= nowlength; j ++)
19     {
20         for(i = 0; i < nowlength; i ++)
21         {
22             a[i] = temp[i];
23         }//恢复操作
24         if(a[j] == b)//发现相同元素,只能留一次排列,其余相同的去除,这个只能处理aaab型的,不能处理aaba型的
25         {
26             step = 1;
27             for(i = j + 1; i < nowlength; i ++)
28             {
29                 if(a[i] == b)
30                 {
31                     step ++;
32                 }
33                 else
34                 {
35                     break;
36                 }
37             }
38         }
39         else
40         {
41             step = 0;
42         }
43         for(i = nowlength; i > j; i --)
44         {
45             a[i] = a[i - 1];
46         }
47         a[j] = b;//插入操作
48         j += step;
49         if(nowlength + 1 < strlen(a))
50         {
51             insert(a, nowlength + 1, a[nowlength + 1]);//如果字符串后面还有字符没有处理完,则继续向下递归
52         }
53         else
54         {
55             printf("%s\n", a);//如果字符串后面没有字符要插入了,则本次递归已经到达底层,输出一个结果
56             count ++;
57         }
58     }
59 }
60 
61 int main()
62 {
63     char a[10];
64 
65     while(scanf("%s",a) != EOF)
66     {
67         count = 0;
68         if(strlen(a) == 1)
69         {
70             printf("%s\n", a);
71             printf("count = 1\n");
72         }
73         else
74         {
75             insert(a, 1, a[1]);
76             printf("count = %d\n",count);
77         }
78     }
79     return 0;
80 }

  第二种思路【完整,可全排列亦可去重】:

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 
 5 int count = 0;
 6 
 7 typedef struct
 8 {
 9     int exist;//这个字符是否存在于字符串中
10     int *used;//这个字符在全排列处理过程中是否在i位置固定过
11 }alphabet;//字母表结构体,用哈希表的形式做记录,可以让查询时间缩短至O(1)。
12 
13 void wholeArray(char *a, char nowlength, alphabet *records)
14 {
15     int i;
16     char guard;
17     alphabet temp[26];
18     int j, t;
19     if(nowlength == 1)
20     {
21         count ++;
22         printf("%s\n", a);
23     }//递归到底层,输出结果
24     else
25     {
26         for(i = 0; i < nowlength; i ++)
27         {
28             if(records[a[i] - 'a'].used[nowlength - 1] == 1)//如果这个字符曾经在该位置固定过,则跳过本轮处理
29             {
30                 continue;
31             }
32             else
33             {
34                 records[a[i] - 'a'].used[nowlength - 1] = 1;
35                 guard = a[nowlength - 1];
36                 a[nowlength - 1] = a[i];
37                 a[i] = guard;
38                 for(j = 0; j < 26; j ++)
39                 {
40                     temp[j].exist = records[j].exist;
41                     if(temp[j].exist == 1)
42                     {
43                         temp[j].used = (int *)malloc(strlen(a) * sizeof(int));
44                         for(t = 0; t < strlen(a); t ++)
45                         {
46                             temp[j].used[t] = records[j].used[t];
47                         }
48                     }
49                 }//用temp表复制records表,并传入到递归的下一层中,以免破换records的值,因为records只有在最顶级的循环处理中才能更改,它表示某一个字符所有的全排列结果都已得到
50                 wholeArray(a, nowlength - 1, temp);
51                 a[i] = a[nowlength - 1];
52                 a[nowlength - 1] = guard;//返回上层递归时要将局部字符串回归原样
53             }
54         }
55     }
56 }
57 
58 int main()
59 {
60     char a[10];
61     alphabet records[26];
62     int i,j;
63     while(scanf("%s", a) != EOF)
64     {
65         for(i = 0; i < 26; i ++)
66         {
67             records[i].exist = 0;
68         }
69         for(i = 0; i < strlen(a); i ++)
70         {
71             if(records[a[i] - 'a'].exist == 0)
72             {
73                 records[a[i] - 'a'].exist = 1;
74                 records[a[i] - 'a'].used = (int *)malloc(strlen(a) * sizeof(int));
75                 for(j = 0; j < strlen(a); j ++)
76                 {
77                     records[a[i] - 'a'].used[j] = 0;
78                 }
79             }
80         }
81         count = 0;
82         wholeArray(a, strlen(a), records);
83         printf("count = %d\n", count);
84     }
85     return 0;
86 }
原文地址:https://www.cnblogs.com/Rafy/p/2962085.html