1. 程式人生 > >紫書第七章-----暴力求解法(全排列演算法)

紫書第七章-----暴力求解法(全排列演算法)

遞迴求全排列

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