前几天,师兄轻描淡写的出了一道题,对于一个给定的字符串,输出它的全排列结果,例如,输入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 }