1.全排列:本文以(1,2,3)为例,共有六种情况:123,132,213,231,312,321
2.递归思路:
此问题可以拆分为求以1开头的全排,以2开头的全排,以3开头的全排。
假设在当前的排列P(1,2,x)中,前两个数字已经排好了,所以只需要在第三个位置插入一个数字x,易知此处应对1~3这三个数字进行枚举,因为前两个数字已经使用过了,所以这里只能使用3.(具体的实现细节后面介绍).用P[index]表示当前要填入位置的数字,index用来表示位置。
再确定一个散列表“hashtable[]=1”用来标注刚刚填入的数字已经被使用过了,比如hashtable[1]=1,表示1这个数字已经被使用过,后面的就不能再用了,借助一个if判断即可实现。若这个数字没被使用,当P[index]被填入数字后,需要递归调用generateP函数,继续进行下一位,当到达递归边界:即index=n+1时,说明三个位置已经全部填完了,这时候输出这个排列即可。
递归条件:generateP(index+1)
递归边界 : index+1 == n
笔者在初学时,由于未完全理解递归的调用过程,故无法理解代码。该递归的具体过程为:
主函数运行generateP(1),调用函数,未到递归边界,进入for循环。
枚举1,因为1这个数字还未使用(hashtable[x]=false),把x=1填入当前位置,标记1已使用,递归调用generateP(2),处理第二个位置,未到递归边界,再进入for循环,枚举1,由于hashtable[1]=true,说明1已被使用,if判断条件不成立,i自增,枚举2,2未被使用,继续下面的语句。
程序不断递归,直到在generateP(3)中运行到generateP(4),到达递归边界,把当前排列123输出。输出后返回,回到调用generateP(4)的位置即generateP(3),继续下面的语句,即hashtable[3]=false.一旦 for 循环结束,generateP(3)
的执行也就结束了,程序会返回到上一级调用,即 generateP(2)
。generateP(2)中,继续运行下一句hashtable,然后注意,此时x=2,还可以枚举x=3,不理解这个过程的可以想一下:第二个位置一共有2种可能,所以回到generateP(2),会进行x=3的循环,由于hashtable[2]已经被恢复,所以这里可填入x=3,继续递归generateP(3),进行第三个位置的枚举。此时1,3这两个数字都已经被使用,所以只能填入2.
至此,132已被填入,以1开头的排列已输出完毕,for循环中x自增到2,继续以2开头的处理。
PS:读者在阅读本文时,可对照下方代码以及注释逐步理解。笔者水平有限,难免存在错误,欢迎指正。
#include<cstdio>
void generateP(int index);
const int maxn = 11;
int n, P[maxn], hashTable[maxn] = {false};
int main() {
n = 3;//需要进行排列的数字个数
generateP(1);//从第一个位置开始处理
return 0;
}
void generateP(int index)
{
int x;
if (index == n+1)//递归边界
{
for (int i = 1; i <= n; i++)
printf("%d ",P[i]);
printf("\n");
return;
}//输出当前排列
for (x = 1; x <= n; x++)
{//x是要填入的数字,用循环进行枚举,选择要填入的数字
if (hashTable[x] == false)//如果x还没有被使用
{
P[index] = x;//把x填入当前位置
hashTable[x] = true;//x填入后把它标记为ture
generateP(index + 1);//继续处理下一个位置 递归调用
hashTable[x] = false;//当前位置处理完毕 把标记还原
}
}
}