对于全排列问题,我们举下面一个例子来分析——
一个char型数组比如有5位,ABCDE,生成其全排列的所有情况,如果用递归的话思路就会非常简单。
递归思想说白了就两步:
1.将第一个字符与后面的字符依次交换顺序;
2.将皮球踢给后来人,即将去掉第一个字符的剩余字符数组看成一个新的数组,并对其进行和之前相同的操作。
贴出代码来瞅瞅:
#include<stdio.h>
#include<string.h>
void f(char *a, int k)
{
int i;
char tmp;
if(k == strlen(a))
{
puts(a);
}
for(i = k; i < strlen(a); i++) //k是被交换的位置
{
tmp = a[k]; a[k] = a[i]; a[i] = tmp; //将第k位与后面的依次交换(比如将首字母和第3个交换)
f(a, k+1); //然后将皮球踢给去掉第k位的剩余部分(去掉首字母的部分)
tmp = a[k]; a[k] = a[i]; a[i] = tmp; //回溯,和刚刚交换的那位换回来(比如和第3位换回来),然后在下一轮循环中才能和后面的(第4位)进行交换
}
}
int main()
{
char a[] = {'A', 'B', 'C', '\0'};
f(a, 0);
return 0;
}
另一种全排列和这个几乎一样,比如“输入一个数字(1-10中的某一个),输出1到这个数字的全排列”,代码如下:
#include<stdio.h>
#define MAX 10
char stack[MAX+1]={0}; //以字符串的形式存放生成的全排列数
int num[MAX]={0};
int N;
void allrank(int m,int n); //全排列数生成函数
int main()
{
int i=0;
printf("Input a number N(1<=N<=10):");
scanf("%d",&N);
allrank(0,N);
return 0;
}
void allrank(int m,int n)
{
int i=0;
if(n==0) //n是剩余的没被标记(使用)的数,如果n=0,意味着数字被用完了
{
stack[N+1]='\0';
puts(stack);
return;
}
for(i=1;i<=N;i++)
{
if(num[i]==0)
{
num[i]=1; //数字i被标记为已使用
stack[m]='0'+i;
allrank(m+1,n-1); //进入了另一个函数
num[i]=0; //数字i被释放 【回溯】
}
}
}
比如N=4,则刚开始i=1,标记为用了,进入内一层函数;
再i=1,发现已经被用了,i++,变为2,把2标记为用了,然后再进入内一层;
同理,i=1,发现被用了,i++,还被用了,i++,变为3,把3标记为用了,进入内一层;
i=1,用了,++,++,++,i=4,发现还没被用,然后把4标记为用了,进入内一层;
此时n=0了,生成一个全排列数1234,return回上一层函数(这个函数里的i的生存期结束,此时上一层函数的i为4);
IMPORTANT----------然后运行第33行代码num[i]=0,则4被释放,i++,则i变为5,跳出循环;
IMPORTANT----------接着函数运行结束(这个函数是void类型,不需要return返回),直接回到了再上一层;
也就是回到了上一层函数的fullrank(m+1,n-1)这一行,此时i=3,接着运行num[i]=0,释放3,i++,变为4,继续进入for循环,if(num[4]==0)为真;
也就是说4没有被使用,把4标记,(此时四位数的前三位确定,为124_),进入内层,i从1循环,发现3没有被用,把3用了,此时变为1243,输出;
之后就不说了。
递归+回溯,其实全排列就是这样。