紫書第七章-----暴力求解法(全排列演算法)
阿新 • • 發佈:2018-12-24
遞迴求全排列
/*
本程式是遞迴實現全排列演算法。
思想是分別讓誰打頭。以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版):
如果某問題的解可以由多個步驟得到,而每個步驟都有若干種選擇(這時候選方案集可能會依賴於先前作出的選擇),且可以用遞迴列舉法實現,則它的工作方式可以用解答樹描述。