全排列


生成1~n的排列

       我們嘗試用遞歸的思想解決:先輸出所有以1開頭的排列(這一步是遞歸調用),然后 輸出以2開頭的排列(又是遞歸調用),接着是以3開頭的排列……最后才是以n開頭的排 列。
        以1開頭的排列的特點是:第一位是1,后面是2~9的排列。根據字典序的定義,這些2 ~9的排列也必須按照字典序排列。換句話說,需要“按照字典序輸出2~9的排列”,不過需 注意的是,在輸出時,每個排列的最前面要加上“1”。這樣一來,所設計的遞歸函數需要以 下參數:
已經確定的“前綴”序列,以便輸出。 需要進行全排列的元素集合,以便依次選做第一個元素。
這樣可得到一個偽代碼:

void print_permutation(序列A, 集合S)
{
    if(S為空) 輸出序列A;
     else 按照從小到大的順序依次考慮S的每個元素v
      {
       print_permutation(在A的末尾填加v后得到的新序列, S-{v});
   }
}      

       暫時不用考慮序列A和集合S如何表示,首先理解一下上面的偽代碼。遞歸邊界是S為空 的情形,這很好理解:現在序列A就是一個完整的排列,直接輸出即可。接下來按照從小到大的順序考慮S中的每個元素,每次遞歸調用以A開頭。

        下面考慮程序實現。不難想到用數組表示序列A,而集合S根本不用保存,因為它可以 由序列A完全確定——A中沒有出現的元素都可以選。C語言中的函數在接受數組參數時無法 得知數組的元素個數,所以需要傳一個已經填好的位置個數,或者當前需要確定的元素位置 cur,代碼如下:

 

#include<iostream>
using namespace std; const int maxn=10000; int a[maxn]; void print_permutation(int n,int a[],int cur) { if(cur==n)     //遞歸邊界 
 { for(int i=0;i<n;i++) cout<<a[i]; cout<<endl; } else { for(int i=1;i<=n;i++)   //嘗試a[cur]中從小到大填1-n各個整數 
 { int ok=1;    //標志元素i是否已經用過 
            for(int j=0;j<cur;j++) if(a[j]==i) ok=0;  //i已經用過,ok標記為0 
            if(ok) { a[cur]=i;   //i未用過,把它添加到序列末尾后繼續遞歸調用 
                print_permutation(n,a,cur+1); } } } } int main() { int n; cin>>n; print_permutation(n,a,0); return 0; }

 

     循環變量i是當前考察的A[cur]。為了檢查元素i是否已經用過,上面的程序用到了一個 標志變量ok,初始值為1(真),如果發現有某個A[j]==i時,則改為0(假)。如果最終ok仍 為1,則說明i沒有在序列中出現過,把它添加到序列末尾(A[cur]=i)后遞歸調用。
     聲明一個足夠大的數組A,然后調用print_permutation(n, A, 0),即可按字典序輸出1~n的 所有排列。

 給定數組的全排列

       如果把問題改成:輸入數組P,並按字典序輸出數組A各元素的所有全排列,則需要對 上述程序進行修改——把P加到print_permutation的參數列表中,然后把代碼中的if(A[j] == i) 和A[cur] = i分別改成if(A[j] == P[i])和A[cur] = P[i]。這樣,只要把P的所有元素按從小到大的
順序排序,然后調用print_permutation(n,  A, 0,p)即可。(注意:此方法數組內元素不可重復)

 

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=10000;
int a[maxn];
void print_permutation(int n,int a[],int cur,int p[])
{
    if(cur==n)     //遞歸邊界 
    {
        for(int i=0;i<n;i++) cout<<a[i];
        cout<<endl;
    }
    else
    {
        for(int i=0;i<n;i++)   //嘗試a[cur]中從小到大填p[i]中的各個整數 
        {
            int ok=1;    //標志元素i是否已經用過 
            for(int j=0;j<cur;j++)
            if(a[j]==p[i]) ok=0;  //p[i]已經用過,ok標記為0 
            if(ok)
            {
                a[cur]=p[i];   //p[i]未用過,把它添加到序列末尾后繼續遞歸調用 
                print_permutation(n,a,cur+1,p);
            }
        }
    }
}
int main()
{
    int p[20];
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>p[i];
    sort(p,p+n);
    print_permutation(n,a,0,p);
    return 0;
}

 

生成可重集的排列

方法一:

      一個解決方法是統計A[0]~A[cur-1]中P[i]的出現次數c1,以及P數組中P[i]的出現次數 c2。只要c1<c2,就能遞歸調用。結果又如何呢?輸入1 1 1,輸出了27個1 1 1。遺漏沒有了,但是出現了重復:先試着把 第1個1作為開頭,遞歸調用結束后再嘗試用第2個1作為開頭,遞歸調用結束后再嘗試用第3 個1作為開頭,再一次遞歸調用。可實際上這3個1是相同的,應只遞歸1次,而不是3次。
       換句話說,我們枚舉的下標i應不重復、不遺漏地取遍所有P[i]值。由於P數組已經排過 序,所以只需檢查P的第一個元素和所有“與前一個元素不相同”的元素,即只需在“for(i = 0; i < n; i++)”和其后的花括號之前加上“if(!i || P[i] != P[i-1])”即可。

 

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=10000;
int a[maxn];
void print_permutation(int n,int a[],int cur,int p[])
{
    if(cur==n)     //遞歸邊界 
    {
        for(int i=0;i<n;i++) cout<<a[i];
        cout<<endl;
    }
    else
    {
        for(int i=0;i<n;i++)   //嘗試a[cur]中從小到大填p[i]中的各個整數 
        {
            if(p[i]!=p[i-1])
            {
            int c1=0,c2=0;
            for(int j=0;j<cur;j++) if(a[j]==p[i]) c1++;
            for(int j=0;j<n;j++)   if(p[j]==p[i]) c2++;
            if(c1<c2)
            {
                a[cur]=p[i];
                print_permutation(n,a,cur+1,p);
            }
        }
        }
    }
}
int main()
{
    int p[20];
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>p[i];
    sort(p,p+n);
    print_permutation(n,a,0,p);
    return 0;
}

 方法二:

      枚舉所有排列的另一個方法是從字典序最小排列開始,不停調用“求下一個排列”的過 程。如何求下一個排列呢?C++的STL中提供了一個庫函數next_permutation。看看下面的代 碼片段,就會明白如何使用它了。

 

#include<cstdio>
#include<algorithm>
using namespace std;
int main( ) {
    int n, p[10];
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &p[i]);
    sort(p, p+n);      //排序,得到p的最小排列 
    do {
        for(int i = 0; i < n; i++) printf("%d ", p[i]);
        printf("\n");
    } while(next_permutation(p, p+n)); 
    return 0;
}
    
    

 

需要注意的是,上述代碼同樣適用於可重集。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM