zoukankan      html  css  js  c++  java
  • 字符串的全排列【递归算法训练】

      前几天,师兄轻描淡写的出了一道题,对于一个给定的字符串,输出它的全排列结果,例如,输入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 }
  • 相关阅读:
    自动刷新页面
    超链接<A>链接到某个文档
    JS脚本的对话框
    表最后更新的标识列
    c#对象私有属性在重载Equals中的使用
    一个关于在Fedora下安装jdk的问题
    表格的行转列
    UNION和UNION ALL的区别
    关于使用存储过程的一些好处以及注意事项[转]
    SQL 外链接操作小结
  • 原文地址:https://www.cnblogs.com/Rafy/p/2962085.html
Copyright © 2011-2022 走看看