递归求全排列
/*
本程序是递归实现全排列算法。
思想是分别让谁打头。以1,2,3,4为例,一共只有4位,
第一位可以分别让1,2,3,4打头,以第一位是1为例,
第二位可以分别让2,3,4打头,以第二位是2为例,
第三位可以分别让3,4打头,以第三位是4为例,
第四位固定是4,输入此排列。
其他情况类似输出。
去重:以序列1,2,2,3为例,一共四位,一共3个不同的数,
第一位可以让1,2,3打头,以第一位是1为例,
第二位可以让2打头,以第二位为2为例,
第三位的时候,由于前两位中已经出现了2,和第三位相同,不能让2第二次打头,所以,
下面的算法为了去除重复排列,没有让1与第三个2交换。
*/
#include<iostream>
#include<algorithm>
using namespace std;
//去重复函数
bool is_swap(int a[],int st,int en){
for(int i=st;i<en;i++){
if(a[i]==a[en]) return false;
}
return true;
}
//全排列算法
void per(int a[],int st,int en){
if(st==en){
for(int i=0;i<en;i++)
cout<<a[i]<<" ";
cout<<endl;
}
else{
for(int i=st;i<en;i++){
if(is_swap(a,st,i)){
swap(a[st],a[i]);
per(a,st+1,en);
swap(a[st],a[i]);//注意这里,解释一下,比如1,2,3,4的
//全排列,以1打头的排列都求出后,重新调整为1,2,3,4,以
//2打头的排列都求出后,重新调整为1,2,3,4等等,内层递归亦如此
}
}
}
}
int main()
{
int a[4]={1,2,2,3};
per(a,0,4);
return 0;
}
STL中的next_permutation求全排列
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int a[4]={1,2,2,3};
sort(a,a+4);
do{
for(int i=0;i<4;i++)
cout<<a[i]<<" ";
cout<<endl;
}while(next_permutation(a,a+4));
return 0;
}
解答树
参考刘汝佳《算法竞赛入门经典》(第2版)
以上面递归求全排列的算法为例,下面的树展示了递归的过程。
第0层:(,,,)
第1层:(1,,,)、(2,,,)、(3,,,)、(4,,,)
第2程:
上面1打头的子结点是(1,2,,)、(1,3,,)、(1,4,,)
上面2打头的子结点是(2,1,,)、(2,3,,)、(2,4,,)
上面3打头的子结点是(3,1,,)、(3,2,,)、(3,4,,)
上面4打头的子结点是(4,1,,)、(4,2,,)、(4,3,,)
……
把上面的画成树,就是解答树。下面求一下解答树的结点个数:
第一层:n
第二层:n*(n-1)
第三层:n*(n-1)*(n-2)
……
第i层:n*(n-1)(n-2)…*(n-(i-1))=n!/(n-i)!
总的结点数是n!*(1/(n-1)!+1/(n-2)!+…+1/1!+1/0!),由泰勒展开式可知该式子趋向e*n!,则总结点数小于e*n!,又低第n层和第n-1层的结点数都是n!,最后两层的结点数目占据了2*n!,所以,多数情形下,解答树上的借电脑几乎全部来源于最后一两层。
参考刘汝佳《算法竞赛入门经典》(第2版):
如果某问题的解可以由多个步骤得到,而每个步骤都有若干种选择(这时候选方案集可能会依赖于先前作出的选择),且可以用递归枚举法实现,则它的工作方式可以用解答树描述。