淺談全排列與組合的生成


例:輸出從1,2......m,中任取k個數的所有組合。m=5,k=3時有543,542,541,532,531,521,432,431,421,321有C(m,k)個。

法一:枚舉方法

 1 #include <iostream>
 2 #include <cstdio>
 3 using namespace std;
 4 int main()
 5 {
 6     int n;
 7     cin >> n;
 8     for (int x = 1; x <= n - 2; ++x)
 9         for (int y = x + 1; y <= n - 1; ++y)
10             for (int z = y + 1; z <= n; ++z)
11                 printf("%d %d %d\n", x, y, z);
12     system("pause");
13     return 0;
14 }

n = 5時將輸出123,124,125,134,135,145,234,235,245,345。

法二:遞歸實現

設函數comb(int m,int k)是找出1,2,.....m中任取k個數的所有組合,當組合的第一個數字選定后,其后的數字的就是從余下的m-1個數選k-1個數的組合,問題轉化為求comb(m-1, k-1).需要一個全局數組存放結果,將確定的k個數字組合的第一個數存儲在a[k]中,求出一個組合后輸出a數組中的一個組合。每個組合的第一個數字可以是m,m-1,.....k.函數確定組合的第一個數字后,如果還為確定組合的其余元素,則繼續遞歸,否則輸出。

 1 #include <iostream>
 2 #include <cstdio>
 3 using namespace std;
 4 const int MAX = 10;
 5 int a[10];
 6 void comb(int m, int k)
 7 {
 8     int i, j;
 9     for (i = m; i >= k; --i)
10     {
11         a[k] = i;                    //每個組合的第一個數可以是m,m-1.....k
12         if (k > 1)
13             comb(i - 1, k - 1);        //k>1繼續遞歸
14         else
15         {
16             for (j = a[0]; j > 0; --j)
17                 printf("%d", a[j]);
18             printf("\n");
19         }
20     }
21 }
22 int main()
23 {
24     int m, k;
25     cin >> m >> k;
26     a[0] = k;                //a[0]用來表示k 
27     comb(m, k);
28     return 0;
29 }

輸入m=5,k=3,輸出543,542,541,532,531,521,432,431,421,321。

全排列的生成:

最簡單的方法就是使用STL<algorithm>中的next_permutation,其返回bool值,不過經常用就啥都忘了。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>            //for sort and next_permutation 
 4 using namespace std;
 5 int main()
 6 {
 7     int n;
 8     scanf("%d", &n);
 9     int p[n];
10     for (int i = 0; i < n; ++i)
11         scanf("%d", &p[i]);
12     sort(p, p + n);                //處理亂序輸入,適用於可重復集合
13     do {
14         for (int i = 0; i < n; ++i)
15             printf("%d ", p[i]);
16         printf("\n");
17     } while (next_permutation(p, p + n));   //bool next_purmutation(begin, end) 
18     system("pause");
19     return 0;
20 } 

遞歸生成1-n的全排列(無重復元素):如abc的全排列有abc,acb,bac,bca,cba,cab,一共3!個。

 設對R={r1,r2,......rn}全排列,設Ri = R - {ri},perm(X)表示集合X的全排序,(ri)perm(X)表示X全排序前加前綴ri

全排列的遞歸定義:設n是集合中元素個數

                  r                     n = 1

perm(R)=  (r1)perm(R1)    

                 ......

      (rn)perm(Rn)

如果給定集合{a,b,c,d},所有可能的排列由下列排列組成

1.以a開頭的后面跟着(b,c,d)的所有排列

2.以b開頭的后面跟着(a,c,d)的所有排列

3.以c開頭的后面跟着(a,b,d)的所有排列

4.以d開頭的后面跟着(a,b,c)的所有排列

遞歸線索是“后面跟着。。。的所有排列”,若能夠解決n-1個元素集合的排列,就可以解決n個元素集合的排列問題。

處理時將所有數分別與第一個數交換,這樣就總是在處理后n-1個數的全排列。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>            //for swap(x,y)
 4 using namespace std;
 5 void perm(char *a, int i, int n)
 6 {
 7     if (i == n)                            //遞歸出口
 8     {
 9         for (int j = 0; j <= n; ++j)
10             printf("%c", a[j]);
11         printf("\n");
12     }
13     else
14     {
15         for (int j = i; j <= n; ++j)
16         {
17             swap(a[i], a[j]);
18             perm(a, i + 1, n);
19             swap(a[i], a[j]);            //第二個swap用於恢復與第一個數交換
20         }
21     }
22 }
23 int main()
24 {
25     char a[3] = {'a', 'b', 'c'};
26     perm(a, 0, 2);                      //初始調用perm(list, 0, n-1)
27     system("pause");
28     return 0;
29 }

執行后輸出abc,acb,bac,bca,cba,cab,按照字典序輸出。

根據這種思路還有一種方法確定遞歸函數:(參考劉汝佳《算法競賽入門經典》)

1已經確定的前綴序列,以便輸出

2需要進行全排列的元素集合,以便依次選作第一個元素

偽代碼:

void perm(序列A, 集合S)

{

  if(S為空)

    輸出序列A;

  else 按照從小到大的順序依次考慮S的每個元素v

    perm(在A的末尾添加v后得到的新序列,S-{v});

}

代碼如下:

 1 #include <iostream>
 2 #include <cstdio>
 3 using namespace std;
 4 void perm(int n, int *a, int cur)
 5 {
 6     int i, j;
 7     if (cur == n)                //遞歸邊界
 8     {
 9         for (i = 0; i < n; ++i)
10             printf("%d", a[i]);  //處理字母時只需要改成printf("%c", a[i]+'a'-1);
11         printf("\n");
12     }
13     else for (i = 1; i <= n; ++i)  //在a[cur]中填各種整數i
14     {
15         bool ok = 1;
16         for (j = 0; j < cur; ++j)
17             if (a[j] == i)
18                 ok = 0;              //若i在a[0]...a[cur-1]出現過,則不能再選
19         if (ok)                      //ok為真說明i沒有在序列中出現過
20         {                         //就把i添加到序列末尾后遞歸調用,這樣處理就不必保存集合S了
21             a[cur] = i;
22             perm(n, a, cur + 1);
23         }
24     }
25 }
26 int main()
27 {
28     int a[] = {1, 2, 3};
29     perm(3, a, 0);                //初始調用perm(n, a, 0)
30     system("pause");
31     return 0;
32 }

輸出結果123,132,213,231,312,321,改成字母后輸出abc,acb,bac,bca,cba,cab。


免責聲明!

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



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