遞歸(四):組合


      排列組合是組合學最基本的概念。所謂排列,就是指從給定個數的元素中取出指定個數的元素進行排序。組合則是指從給定個數的元素中僅僅取出指定個數的元素,不考慮排序。

      排列與組合在日常生活中應用較廣,比如在考慮某些事物在某種情況下出現的次數時,往往需要用到排列和組合。

【例1】取值組合。

      有一個集合擁有m個元素{1,2,…,m},任意的從集合中取出n個元素,則這n個元素所形成的可能子集有哪些?

假設有5個元素的集合,取出3個元素的可能子集如下:

          {1,2,3}、{1,2,4}、{1,2,5}、{1,3,4}、{1,3,5}、

          {1,4,5}、{2,3,4}、{2,3,5}、{2,4,5}、{3,4,5}。

      編寫一個程序,輸入整數m和n,輸出m個元素中,取出n個元素的各種情況。

      (1)編程思路。

      用遞歸程序來解決問題。

      從m個數中,取出n個數的所有組合。設m個數已存於數組A[1..m]中。為使結果唯一,可以分別求出包括A[m]和不包括A[m]的所有組合。即包括A[m]時,求出從A[1..m-1]中取出n-1個元素的所有組合(子問題,遞歸);不包括A[m]時,求出從A[1..m-1]中取出n個元素的所有組合(同樣是子問題,遞歸)。

    (2)源程序。

#include <iostream> 

using namespace std; 

#define MAXSIZE 20

void nkcombination(int i,int k,int j,int a[],int b[])

// 從n(a[0])個數中連續取出k個數的所有組合,n個數已存入數組A中。

// i為待取數在數組A中的下標,j為結果數組B中的下標。

{

         if (k==0)

        {

               for (int p=1;p<=b[0];p++)

                      cout<<b[p]<<" ";

               cout<<endl;

        }

        else if (i+k-1<=a[0])

        {

               b[j]=a[i];     j++;

               nkcombination(i+1,k-1,j,a,b);  

               // 包括A[i]時,遞歸求出從A[i+1..n]中取出k-1個元素的所有組合

               nkcombination(i+1,k,j-1,a,b);  

              // 不包括A[i]時,遞歸求出從A[i+1..n]中取出k個元素的所有組合

        }

}

int main() 

    int set[MAXSIZE],result[MAXSIZE];

    int m,n,i;

    cout<<"輸入給定元素個數 m :";

    cin>>m;

    for (i=1;i<=m;i++)

           set[i]=i;

    cout<<"輸入取出元素個數 n:";

    cin>>n;

    set[0]=m;     result[0]=n;

    nkcombination(1,n,1,set,result);

    return 0; 

}  

【例2】選數。

      已知n個整數x1,x2,…,xn,以及一個整數k(k<n)。從n個整數中任選k個整數相加,可分別得到一系列的和。例如當n=4,k=3,4個整數分別為3,7,12,19時,可得全部的組合與它們的和為:3+7+12=22,3+7+19=29,7+12+19=38,3+12+19=34。編寫一個程序計算出和為質數的組合共有多少種。

      例如上例,只有一種組合的和為質數:3+7+19=29。

      (1)編程思路。

      本例是求從n個數中選k個數的組合,並使其和為素數。求解此題時,先按例1的方法生成k個數的組合,再判斷k個數的和是否為質數,若為質數則輸出和式並計數。

      (2)源程序。

#include <iostream>

#include <cmath>

using namespace std;

#define MAXSIZE 20

int cnt=0;

bool isPrime(int num)

{

    int m;

    if(num==2) return true;

    for(m=2;m<=(int)sqrt((double)num);m++)

        if (num%m==0)

         return false;

    return true;

}

void nkcombination(int i,int k,int j,int a[],int b[])

{

     if (k==0)

     {        

               int p,s;

             for (p=1,s=0;p<=b[0];p++)

                 s=s+b[p];

             if (isPrime(s))

             {

                    cnt++;

                   for (p=1;p<b[0];p++)

                       cout<<b[p]<<" + ";

                   cout<<b[p]<<"="<<s<<endl;

              }

     }

     else if (i+k-1<=a[0])

     {

               b[j]=a[i];  j++;

               nkcombination(i+1,k-1,j,a,b); 

               nkcombination(i+1,k,j-1,a,b); 

        }

}

int main()

{

    int set[MAXSIZE],result[MAXSIZE];

    int n,k,i;

    cout<<"輸入給定整數的個數 n :";

    cin>>n;

    cout<<"依次輸入"<<n<<"個整數:";

    for (i=1;i<=n;i++)

                cin>>set[i];

            cout<<"輸入取出元素個數 k:";

    cin>>k;

    set[0]=n;     result[0]=k;

    nkcombination(1,k,1,set,result);

    cout<<"Count="<<cnt<<endl;

    return 0;

}  

【例3】Lotto (POJ 2245)。

Description

In the German Lotto you have to select 6 numbers from the set {1,2,...,49}. A popular strategy to play Lotto - although it doesn't increase your chance of winning - is to select a subset S containing k (k > 6) of these 49 numbers, and then play several games with choosing numbers only from S. For example, for k=8 and S = {1,2,3,5,8,13,21,34} there are 28 possible games: [1,2,3,5,8,13], [1,2,3,5,8,21], [1,2,3,5,8,34], [1,2,3,5,13,21], ... [3,5,8,13,21,34].

Your job is to write a program that reads in the number k and the set S and then prints all possible games choosing numbers only from S.

Input

The input will contain one or more test cases. Each test case consists of one line containing several integers separated from each other by spaces. The first integer on the line will be the number k (6 < k < 13). Then k integers, specifying the set S, will follow in ascending order. Input will be terminated by a value of zero (0) for k.

Output

For each test case, print all possible games, each game on one line. The numbers of each game have to be sorted in ascending order and separated from each other by exactly one space. The games themselves have to be sorted lexicographically, that means sorted by the lowest number first, then by the second lowest and so on, as demonstrated in the sample output below. The test cases have to be separated from each other by exactly one blank line. Do not put a blank line after the last test case.

Sample Input

8 1 2 3 5 8 13 21 34

0

Sample Output

1 2 3 5 8 13

1 2 3 5 8 21

1 2 3 5 8 34

1 2 3 5 13 21

1 2 3 5 13 34

1 2 3 5 21 34

1 2 3 8 13 21

1 2 3 8 13 34

1 2 3 8 21 34

1 2 3 13 21 34

1 2 5 8 13 21

1 2 5 8 13 34

1 2 5 8 21 34

1 2 5 13 21 34

1 2 8 13 21 34

1 3 5 8 13 21

1 3 5 8 13 34

1 3 5 8 21 34

1 3 5 13 21 34

1 3 8 13 21 34

1 5 8 13 21 34

2 3 5 8 13 21

2 3 5 8 13 34

2 3 5 8 21 34

2 3 5 13 21 34

2 3 8 13 21 34

2 5 8 13 21 34

3 5 8 13 21 34

       (1)編程思路。

      本題的意思是要在有k個元素的集合S中任意取6個元素,並輸出所有這些取值組合。

     設 combine(int take[], int len, int count,int num[])為從具有len個元素的數組num中取出count個元素,並將取出的元素存放在數組a中。

       為求解combine(int take[], int len, int count,int num[]),可以先在len個元素的數組num的后面取第一個元素num[i]放在a[count-1]中,所取的第一個數組元素的下標i可以是len-1,len-2,…,count-1。注意:第一個取的數組元素的下標i不能取count-2,因為后面的要取的元素均會在第一個取的元素的前面,因此最多只能取出0~count-3共count-2個不同的y元素,達不到取count個數的目的。

       在將確定組合的第一個元素num[i]放入數組take后,有兩種選擇:還未確定組合的其余元素時(count>1,即還需取count-1個元素),繼續遞歸comb(take,i,count-1,num)確定組合的其余元素,即在num[0]~num[i-1]這i個元素中取count-1個數;已確定組合的全部元素時(count==1),輸出這個組合。

      (2)源程序。

#include <iostream>

using namespace std;

void combine(int take[], int len, int count,int num[])

{

    int i,j;

    for (i = len-1; i >= count-1; i--)

    {

       take[count - 1] = num[i];

       if (count >1)

           combine(take, i , count - 1, num);

       else

      {

          for (j = 6-1; j >=0; j--)

          {

               cout<<take[j]<<" ";

           }

           cout<<endl;

      }

    }

}

int main()

{

       int i,k,num[13],take[6],t;

       while(cin >> k && k!= 0)

       {

             for(i = 0; i < k; i++)

                  cin >> num[i];

              for (i=0;i<k/2;i++)

              {

                     t=num[i];

                     num[i]=num[k-i-1];

                     num[k-i-1]=t;

              }

              combine(take,k,6,num);

              cout << endl;

    }

   return 0;

}


免責聲明!

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



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