user-0 提问: 有关一个全排列（递归）的问题

回复:
user: user-1
content: 楼主是个有志青年！

user: user-0
content: 引用 3 楼 zhao4zhong1 的回复:再供参考：//全排列的生成算法 //  全排列的生成算法就是对于给定的字符集，用有效的方法将所有可能的全排列无重复无遗漏地枚举出来。任何n个字符集的排列都可以与1～n的n个数字的排列一一对应， //  因此在此就以n个数字的排列为例说明排列的生成法。 //  n个字符的全体排列之间存在一个确定的线性顺序关系。所有的排列中除最后一个排列外，都有一个后继；除第一个排列外，都有一个前驱。每个排列的后继都可以从 //  它的前驱经过最少的变化而得到，全排列的生成算法就是从第一个排列开始逐个生成所有的排列的方法。 //  全排列的生成法通常有以下几种： //  字典序法 //  递增进位数制法 //  递减进位数制法 //  邻位交换法 //  递归类算法 //1.字典序法 效率高且顺序自然 //  字典序法中，对于数字1、2、3......n的排列，不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的。例如对于5个数字的排列12354和12345，排列12345在前， //  排列12354在后。按照这样的规定，5个数字的所有的排列中最前面的是12345，最后面的是54321。 //  字典序算法如下： //  设P是1～n的一个全排列:p=p1p2......pn=p1p2......pj-1pjpj+1......pk-1pkpk+1......pn //  1）从排列的右端开始，找出第一个比右边数字小的数字的序号j（j从左端开始计算），即 j=max{i|pi<pi+1} //  2）在pj的右边的数字中，找出所有比pj大的数中最小的数字pk，即 k=max{i|pi>pj}（右边的数从右至左是递增的，因此k是所有大于pj的数字中序号最大者） //  3）对换pi，pk //  4）再将pj+1......pk-1pkpk+1pn倒转得到排列p’’=p1p2.....pj-1pjpn.....pk+1pkpk-1.....pj+1，这就是排列p的下一个下一个排列。 //  例如839647521是数字1～9的一个排列。从它生成下一个排列的步骤如下： //  自右至左找出排列中第一个比右边数字小的数字4 839647521 //  在该数字后的数字中找出比4大的数中最小的一个5 839647521 //  将5与4交换 839657421 //  将7421倒转 839651247 //  所以839647521的下一个排列是839651247。 //2.递增进位数制法 //  在递增进位制数法中，从一个排列求另一个排列需要用到中介数。如果用 ki表示排列p1p2...pi...pn中元素pi的右边比pi小的数的个数，则排列的中介数就是对应的 //  排列k1 ...... ki...... kn-1。 //  例如排列839647521的中介数是72642321，7、2、6、......分别是排列中数字8、3、9、......的右边比它小的数字个数。 //  中介数是计算排列的中间环节。已知一个排列，要求下一个排列，首先确定其中介数，一个排列的后继，其中介数是原排列中介数加1，需要注意的是，如果中介数的 //  末位kn-1+1=2，则要向前进位，一般情形，如果ki+1=n-i+1，则要进位，这就是所谓的递增进位制。例如排列839647521的中介数是72642321，则下一个排列的中介数 //  是67342221+1=67342300（因为1+1=2，所以向前进位，2+1=3，又发生进位，所以下一个中介数是67342300）。 //  得到中介数后，可根据它还原对应得排列。 //  算法如下： //  中介数k1、k2、......、kn-1的各位数字顺序表示排列中的数字n、n-1、......、2在排列中距右端的的空位数，因此，要按k1、k2、......、kn-1的值从右向左 //  确定n、n-1、......、2的位置，并逐个放置在排列中：i放在右起的ki+1位，如果某位已放有数字，则该位置不算在内，最后一个空位放1。 //  因此从67342300可得到排列849617523，它就是839647521的后一个排列。因为9最先放置，k1=6，9放在右起第7位，空出6个空位，然后是放8，k2=7，8放在右起第8位， //  但9占用一位，故8应放在右起第9位，余类推。 //3.递减进位制数法 //  在递增进位制数法中，中介数的最低位是逢2进1，进位频繁，这是一个缺点。把递增进位制数翻转,就得到递减进位制数。 //  839647521的中介数是67342221(k1k2...kn-1)，倒转成为12224376(kn-1...k2k1)，这是递减进位制数的中介数：ki(i=n-1,n-2,...,2)位逢i向ki-1位进1。给定排列p， //  p的下一个排列的中介数定义为p的中介数加1。例如p=839647521，p的中介数为12224376，p的下一个排列的中介数为12224376+1=12224377，由此得到p的下一个 //  排列为893647521。 //  给定中介数，可用与递增进位制数法类似的方法还原出排列。但在递减进位制数中，可以不先计算中介数就直接从一个排列求出下一个排列。具体算法如下： //  1）如果p(i)=n且i<>n，则p(i)与p(i-1)交换 //  2）如果p(n)=n，则找出一个连续递减序列9、8、......、i，将其从排列左端删除，再以相反顺序加在排列右端，然后将i-1与左边的数字交换 //  例如p=893647521的下一个排列是983647521。求983647521的下一个排列时，因为9在最左边且第2位为8，第3位不是7，所以将8和9从小到大排于最右端364752189， //  再将7与其左方数字对调得到983647521的下一个排列是367452189。又例如求987635421的下一个排列，只需要将9876从小到大排到最右端并将5与其左方数字3对调， //  得到534216789。 //4.邻位对换法 效率最高但顺序不自然 //  邻位对换法中下一个排列总是上一个排列某相邻两位对换得到的。以4个元素的排列为例，将最后的元素4逐次与前面的元素交换，可以生成4个新排列： //  1 2 3 4, 1 2 4 3, 1 4 2 3, 4 1 2 3 //  然后将最后一个排列的末尾的两个元素交换，再逐次将排头的4与其后的元素交换，又生成四个新排列： //  4 1 3 2, 1 4 3 2, 1 3 4 2, 1 3 2 4 //  再将最后一个排列的开头的两个元素交换，将4从后往前移： //  3 1 2 4, 3 1 4 2, 3 4 1 2, 4 3 1 2 //  如此循环4!次既可求出全部排列。 //5.元素增值法（n进制法）效率低 //  1）从原始排列p=p1p2......pn开始，第n位加n-1，如果该位的值超过n，则将它除以n，用余数取代该位，并进位（将第n-1位加1） //  2）再按同样方法处理n-1位，n-2位，......，直至不再发生进位为止，处理完一个排列就产生了一个新的排列 //  3）将其中有相同元素的排列去掉 //  4）当第一个元素的值>n则结束 //  以3个数1、2、3的排列为例：原始排列是1 2 3，从它开始，第3个元素是3，3+2=5，5 Mod 3=2，第2个元素是2，2+1=3，所以新排列是1 3 2。通过元素增值，顺序产生的 //  排列是：1 2 3，1 3 2，2 1 1，2 1 3，2 2 2，2 3 1，2 3 3，3 1 2，3 2 1 //  有下划线的排列中存在重复元素，丢弃，余下的就是全部排列。 //6.递归类算法 //  全排列的生成方法用递归方式描述比较简洁，实现的方法也有多种。 //  1）回溯法 //  回溯法通常是构造一颗生成树。以3个元素为例；树的节点有个数据，可取值是1、2、3。如果某个为0，则表示尚未取值。 //  初始状态是(0，0，0)，第1个元素值可以分别挑选1，2，3，因此扩展出3个子结点。用相同方法找出这些结点的第2个元素的可能值，如此反复进行，一旦出现新结点的3个 //  数据全非零，那就找到了一种全排列方案。当尝试了所有可能方案，即获得了问题的解答。 //  2）递归算法 //  如果用P表示n个元素的排列，而Pi表示不包含元素i的排列，(i)Pi表示在排列Pi前加上前缀i的排列，那么，n个元素的排列可递归定义为： //  如果n=1，则排列P只有一个元素i //  如果n>1，则排列P由排列(i)Pi构成（i=1、2、....、n-1）。 //  根据定义，容易看出如果已经生成了k-1个元素的排列，那么，k个元素的排列可以在每个k-1个元素的排列Pi前添加元素i而生成。例如2个元素的排列是1 2和2 1，对与个 //  元素而言，p1是2 3和3 2，在每个排列前加上1即生成1 2 3和1 3 2两个新排列，p2和p3则是1 3、3 1和1 2、2 1，按同样方法可生成新排列2 1 3、2 3 1和3 1 2、3 2 1。 //  3）循环移位法 //  如果已经生成了k-1个元素的排列，则在每个排列后添加元素k使之成为k个元素的排列，然后将每个排列循环左移（右移），每移动一次就产生一个新的排列。 //  例如2个元素的排列是1 2和2 1。在1 2 后加上3成为新排列1 2 3，将它循环左移可再生成新排列2 3 1、3 1 2，同样2 1 可生成新排列2 1 3、1 3 2和3 2 1。   谢谢讲解，很详细，准备将每种方法都做一遍。

user: user-1
content: 再供参考：//全排列的生成算法 //  全排列的生成算法就是对于给定的字符集，用有效的方法将所有可能的全排列无重复无遗漏地枚举出来。任何n个字符集的排列都可以与1～n的n个数字的排列一一对应， //  因此在此就以n个数字的排列为例说明排列的生成法。 //  n个字符的全体排列之间存在一个确定的线性顺序关系。所有的排列中除最后一个排列外，都有一个后继；除第一个排列外，都有一个前驱。每个排列的后继都可以从 //  它的前驱经过最少的变化而得到，全排列的生成算法就是从第一个排列开始逐个生成所有的排列的方法。 //  全排列的生成法通常有以下几种： //  字典序法 //  递增进位数制法 //  递减进位数制法 //  邻位交换法 //  递归类算法 //1.字典序法 效率高且顺序自然 //  字典序法中，对于数字1、2、3......n的排列，不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的。例如对于5个数字的排列12354和12345，排列12345在前， //  排列12354在后。按照这样的规定，5个数字的所有的排列中最前面的是12345，最后面的是54321。 //  字典序算法如下： //  设P是1～n的一个全排列:p=p1p2......pn=p1p2......pj-1pjpj+1......pk-1pkpk+1......pn //  1）从排列的右端开始，找出第一个比右边数字小的数字的序号j（j从左端开始计算），即 j=max{i|pi<pi+1} //  2）在pj的右边的数字中，找出所有比pj大的数中最小的数字pk，即 k=max{i|pi>pj}（右边的数从右至左是递增的，因此k是所有大于pj的数字中序号最大者） //  3）对换pi，pk //  4）再将pj+1......pk-1pkpk+1pn倒转得到排列p’’=p1p2.....pj-1pjpn.....pk+1pkpk-1.....pj+1，这就是排列p的下一个下一个排列。 //  例如839647521是数字1～9的一个排列。从它生成下一个排列的步骤如下： //  自右至左找出排列中第一个比右边数字小的数字4 839647521 //  在该数字后的数字中找出比4大的数中最小的一个5 839647521 //  将5与4交换 839657421 //  将7421倒转 839651247 //  所以839647521的下一个排列是839651247。 //2.递增进位数制法 //  在递增进位制数法中，从一个排列求另一个排列需要用到中介数。如果用 ki表示排列p1p2...pi...pn中元素pi的右边比pi小的数的个数，则排列的中介数就是对应的 //  排列k1 ...... ki...... kn-1。 //  例如排列839647521的中介数是72642321，7、2、6、......分别是排列中数字8、3、9、......的右边比它小的数字个数。 //  中介数是计算排列的中间环节。已知一个排列，要求下一个排列，首先确定其中介数，一个排列的后继，其中介数是原排列中介数加1，需要注意的是，如果中介数的 //  末位kn-1+1=2，则要向前进位，一般情形，如果ki+1=n-i+1，则要进位，这就是所谓的递增进位制。例如排列839647521的中介数是72642321，则下一个排列的中介数 //  是67342221+1=67342300（因为1+1=2，所以向前进位，2+1=3，又发生进位，所以下一个中介数是67342300）。 //  得到中介数后，可根据它还原对应得排列。 //  算法如下： //  中介数k1、k2、......、kn-1的各位数字顺序表示排列中的数字n、n-1、......、2在排列中距右端的的空位数，因此，要按k1、k2、......、kn-1的值从右向左 //  确定n、n-1、......、2的位置，并逐个放置在排列中：i放在右起的ki+1位，如果某位已放有数字，则该位置不算在内，最后一个空位放1。 //  因此从67342300可得到排列849617523，它就是839647521的后一个排列。因为9最先放置，k1=6，9放在右起第7位，空出6个空位，然后是放8，k2=7，8放在右起第8位， //  但9占用一位，故8应放在右起第9位，余类推。 //3.递减进位制数法 //  在递增进位制数法中，中介数的最低位是逢2进1，进位频繁，这是一个缺点。把递增进位制数翻转,就得到递减进位制数。 //  839647521的中介数是67342221(k1k2...kn-1)，倒转成为12224376(kn-1...k2k1)，这是递减进位制数的中介数：ki(i=n-1,n-2,...,2)位逢i向ki-1位进1。给定排列p， //  p的下一个排列的中介数定义为p的中介数加1。例如p=839647521，p的中介数为12224376，p的下一个排列的中介数为12224376+1=12224377，由此得到p的下一个 //  排列为893647521。 //  给定中介数，可用与递增进位制数法类似的方法还原出排列。但在递减进位制数中，可以不先计算中介数就直接从一个排列求出下一个排列。具体算法如下： //  1）如果p(i)=n且i<>n，则p(i)与p(i-1)交换 //  2）如果p(n)=n，则找出一个连续递减序列9、8、......、i，将其从排列左端删除，再以相反顺序加在排列右端，然后将i-1与左边的数字交换 //  例如p=893647521的下一个排列是983647521。求983647521的下一个排列时，因为9在最左边且第2位为8，第3位不是7，所以将8和9从小到大排于最右端364752189， //  再将7与其左方数字对调得到983647521的下一个排列是367452189。又例如求987635421的下一个排列，只需要将9876从小到大排到最右端并将5与其左方数字3对调， //  得到534216789。 //4.邻位对换法 效率最高但顺序不自然 //  邻位对换法中下一个排列总是上一个排列某相邻两位对换得到的。以4个元素的排列为例，将最后的元素4逐次与前面的元素交换，可以生成4个新排列： //  1 2 3 4, 1 2 4 3, 1 4 2 3, 4 1 2 3 //  然后将最后一个排列的末尾的两个元素交换，再逐次将排头的4与其后的元素交换，又生成四个新排列： //  4 1 3 2, 1 4 3 2, 1 3 4 2, 1 3 2 4 //  再将最后一个排列的开头的两个元素交换，将4从后往前移： //  3 1 2 4, 3 1 4 2, 3 4 1 2, 4 3 1 2 //  如此循环4!次既可求出全部排列。 //5.元素增值法（n进制法）效率低 //  1）从原始排列p=p1p2......pn开始，第n位加n-1，如果该位的值超过n，则将它除以n，用余数取代该位，并进位（将第n-1位加1） //  2）再按同样方法处理n-1位，n-2位，......，直至不再发生进位为止，处理完一个排列就产生了一个新的排列 //  3）将其中有相同元素的排列去掉 //  4）当第一个元素的值>n则结束 //  以3个数1、2、3的排列为例：原始排列是1 2 3，从它开始，第3个元素是3，3+2=5，5 Mod 3=2，第2个元素是2，2+1=3，所以新排列是1 3 2。通过元素增值，顺序产生的 //  排列是：1 2 3，1 3 2，2 1 1，2 1 3，2 2 2，2 3 1，2 3 3，3 1 2，3 2 1 //  有下划线的排列中存在重复元素，丢弃，余下的就是全部排列。 //6.递归类算法 //  全排列的生成方法用递归方式描述比较简洁，实现的方法也有多种。 //  1）回溯法 //  回溯法通常是构造一颗生成树。以3个元素为例；树的节点有个数据，可取值是1、2、3。如果某个为0，则表示尚未取值。 //  初始状态是(0，0，0)，第1个元素值可以分别挑选1，2，3，因此扩展出3个子结点。用相同方法找出这些结点的第2个元素的可能值，如此反复进行，一旦出现新结点的3个 //  数据全非零，那就找到了一种全排列方案。当尝试了所有可能方案，即获得了问题的解答。 //  2）递归算法 //  如果用P表示n个元素的排列，而Pi表示不包含元素i的排列，(i)Pi表示在排列Pi前加上前缀i的排列，那么，n个元素的排列可递归定义为： //  如果n=1，则排列P只有一个元素i //  如果n>1，则排列P由排列(i)Pi构成（i=1、2、....、n-1）。 //  根据定义，容易看出如果已经生成了k-1个元素的排列，那么，k个元素的排列可以在每个k-1个元素的排列Pi前添加元素i而生成。例如2个元素的排列是1 2和2 1，对与个 //  元素而言，p1是2 3和3 2，在每个排列前加上1即生成1 2 3和1 3 2两个新排列，p2和p3则是1 3、3 1和1 2、2 1，按同样方法可生成新排列2 1 3、2 3 1和3 1 2、3 2 1。 //  3）循环移位法 //  如果已经生成了k-1个元素的排列，则在每个排列后添加元素k使之成为k个元素的排列，然后将每个排列循环左移（右移），每移动一次就产生一个新的排列。 //  例如2个元素的排列是1 2和2 1。在1 2 后加上3成为新排列1 2 3，将它循环左移可再生成新排列2 3 1、3 1 2，同样2 1 可生成新排列2 1 3、1 3 2和3 2 1。

user: user-1
content: “给定一个小点的输入，完整单步跟踪(同时按Alt+7键查看Call Stack里面从上到下列出的对应从里层到外层的函数调用历史)一遍。”是理解递归函数工作原理的不二法门！ 递归函数关注以下几个因素 ·退出条件 ·参数有哪些 ·返回值是什么 ·局部变量有哪些 ·全局变量有哪些 ·何时输出 ·会不会导致堆栈溢出  仅供参考：//qplw.cpp #include <stdio.h> #include <string.h> #include <stdlib.h> int v=0; int w=0; int m;//记录字符串长度 int n;//记录字符串中的字符种类数 char map[256];//记录是哪几种字符 int count[256];//记录每种字符有多少个 int stack[1000];//递归用的栈，并记录当前生成的排列 void Make_Map(char *str) {//统计字符串的相关信息     int s[256];     int i;     memset(s,0,sizeof(s));     memset(count,0,sizeof(count));     m=strlen(str);     if (w<1 || m<w) w=m;     while(*str) {         s[*str]++;         str++;     }     n=0;     for (i=0;i<256;i++)         if (s[i]) {             map[n]=i;             count[n]=s[i];             n++;         } } void Find(int depth) {//递归式回溯法生成全排列     if (depth==w) {         int i;         for (i=0;i<depth;i++) putchar(map[stack[i]]);         putchar('\n');     } else {         int i;         if (v && depth>0) {             for (i=0;i<depth;i++) putchar(map[stack[i]]);             putchar('\n');         }         for (i=0;i<n;i++)             if (count[i]) {                 stack[depth]=i;                 count[i]--;                 Find(depth+1);                 count[i]++;             }     } } void main(int argc,char**argv) {     if (argc<2) {         printf("%s 要产生全排列的字符串 [限定长度|-1]\n",argv[0]);         return;     }     if (argc>=3) w=atoi(argv[2]);     if (-1==w) v=1;     Make_Map(argv[1]);     Find(0); } //C:\test>qplw //qplw 要产生全排列的字符串 [限定长度|-1] // //C:\test>qplw 123 //123 //132 //213 //231 //312 //321 // //C:\test>qplw 123 2 //12 //13 //21 //23 //31 //32 // //C:\test>qplw 122333 3 //122 //123 //132 //133 //212 //213 //221 //223 //231 //232 //233 //312 //313 //321 //322 //323 //331 //332 //333 // //C:\test>qplw 123 -1 //1 //12 //123 //13 //132 //2 //21 //213 //23 //231 //3 //31 //312 //32 //321 //

user: user-2
content: 这个思想就是把后面的元素和第i个逐次交换，然后产生后面的排列，递归刚开始都是这种似懂非懂的感觉，看的多了就习惯了，不要想太深太多了，否则容易陷进去反而用不好递归