(转)计算组合数--整数拆分

出自http://hi.baidu.com/xun1573/blog/item/4878002796f8ea07918f9dfd.html

1.计算组合数: 一般常用C(n+1,k)=(n+1)!/k!/(n+1-k)!来计算组合数,但是这个方法中涉及到阶乘运算,数据n不能太大。用下面的方法则可以避免这个问题。

帕斯卡恒等式为C(n+1,k)=C(n,k-1)+C(n,k)

#include <stdio.h>
#include <stdlib.h>
void error()
{
printf("Something is wrong,Please check it!");
exit(1);
}

int fun(int n,int m)
{
   if(n<0||m<0)
     error();
   if(n<m) return 0;
   if(m==0||m==n)
     return 1;
   else
     return fun(n-1,m)+fun(n-1,m-1);
}

int main()
{
   int n,m;
   printf("Input two data:\n");
   scanf("%d%d",&n,&m);
   printf("The combo number is:\n%d\n",fun(n,m));
   return 0;
}

前面的代码还是有点麻烦,递归次数太多而影响效率,例如要计算C(5,1),按照帕斯卡公式应该拆分如下:

C(5,1) =C(4,1)+C(4,0)=C(3,1)+C(3,0)+C(4,0)=C(2,1)+C(2,0)+C(3,0)+C(4,0)=C(1,1)+C(1,0)+C(2,0)+C(3,0)+C(4,0)=5

可以想象,如果把其中的代码写成下面的样子应该会好一些:

long fun(long n,long r)
{
   if(r<0||n<0)//异常处理
   {
      printf("\nError.Exit!");
      exit(-1);
   }
   if(r>n)//数学定义
      return 0;
   if(r==1||r==n-1)//根据组合定义,我们有C(n,1)=C(n,n-1)=n
      return n;
   if(r==0||r==n)//根据组合定义,我们有C(n,0)=C(n,n)=1
      return 1;
   return fun(n-1,r)+fun(n-1,r-1);//帕斯卡组合公式
}

如果按照这个思路延续下去,我们完全可以在代码中写出if(r==2||r==n-2)、if(r==3||r==n-3)甚至更高,但这样的话,就变成数学问题了,就不是程序设计了。最后我们再看看组合数的定义公式C(n,r)=n!/r!/(n-r)!,改写一下:

C(n,r)=n×(n-1)×(n-2)×...×2×1/[r×(r-1)×(r-2)×...×2×1]/[(n-r)×(n-r-1)×(n-r-2)×...×2×1]

可以看出,有些数如n到max(r,n-r)只有分子上有,有些数如max(r,n-r)到min(r,n-r)分子分母都有,而另一些数如min(r,n-r)到1分子上出现一次而分母上出现两次。因而,我把代码写成下面的样子:

long max(long m,long n)
{
   if(m>n)
      return m;
   return n;
}

long min(long m,long n)
{
   if(m<n)
      return m;
   return n;
}

long newfun(long n,long r)
{
   long i;
   long result=1;
   for(i=n;i>0;i--)
   {
      if(i>max(r,n-r))
          result*=i;
      else if(i<=min(r,n-r))
         result/=i;
   }
   return result;
}
写成这样是否就完美了呢?我认为还没有。仔细看一下,就会发现,在最后一个程序中多次调用max函数和min函数,很明显会影响程序效率。考虑了一下,利用“空间”来换取“时间”,我又修改成了下面的样子:

long max(long m,long n)
{
   if(m>n)
      return m;
   return n;
}

long min(long m,long n)
{
   if(m<n)
      return m;
   return n;
}

long newfun(long n,long r)
{
   long i;
   long maxvalue,minvalue;
   long result=1;
   maxvalue=max(r,n-r);
   minvalue=min(r,n-r);
   for(i=n;i>0;i--)
   {
      if(i>maxvalue)
        result*=i;
      else if(i<=minvalue)
        result/=i;
   }
   return result;
}

2. 计算组合:利用二进制数计算无重复组合

#define N 5

#i nclude <conio.h>

void init(int arr[])
{ /* to initiate the array*/
int i;
for(i=0;i<N;i++)
    arr[i]=i+1;
}

void next(int arr[])
{ /* to compute the next possible combination*/
int i;
for(i=N-1;i>=0&&arr[i]==1;i--)
       arr[i]=0;
if(i>=0)
     arr[i]=1;
}

int ZeroAtPos(int arr[])
{ /* return the position of 0 at the 'binary' array,if not,return -1*/
int i;
for(i=0;i<N;i++)
    if(arr[i]==0)
      return i;
return -1;
}

void output(int arr[],int binary[])
{ /* print the current combination according to 'binary' array*/
int i;
for(i=0;i<N;i++)
    if(binary[i]==1)
      printf("%4d",arr[i]);
printf("\n");
}

main()
{
   int array[N];
   int binary[N]={0};
   clrscr();
   init(array);
   while(ZeroAtPos(binary)!=-1)
   {
     next(binary);
     output(array,binary);
   }
}

3. 计算组合:利用函数递归计算无重复组合

int n,r;
int a[10];

void fun(int m,int k)
{
   int i,j;
   for(i=m;i>=k;i--)
   {
      a[k-1]=i;
      if(k>1)
           fun(i-1,k-1);
      else
      {
         for(j=r-1;j>=0;j--)
            printf("%5d",a[j]);
         printf("\n");
      }
   }
}

void main()
{
n=5;
for(r=1;r<=n;r++)
    fun(n,r);
}

4. 计算组合:利用函数递归计算有重复组合

int r;
int result[20]={0};
zuhe(int m,int k)
{
   int i,j;
   for(i=m;i>=0;i--)
   {
      result[k-1]=i;
      if(k>1)
           zuhe(i,k-1);
      else
      {
          for(j=0;j<r;j++)
                printf("%4d",result[j]);
          printf("\n");
      }
   }
}

main()
{
   r=3;
   zuhe(7,r);
}

5. 计算组合:常规算法计算无重复组合

从n个数中取r个数的组合,假设c1c2c3...cr是其中一个组合,则计算其下一个组合的算法如下:

Step 1:求满足不等式cj<n-r+j的最大坐标,并设为i。即i=max{j | cj<n-r+j}

Step 2:设置ci=ci+1

Step 3:设置cj=cj-1+1,对于j=i+1,i+2,i+3,...,r

#i nclude <conio.h>

int fun(int choice[],int r,int n)
{//利用了上面的算法,计算下一个组合
int i,j;
for(i=r-1;i>=0;i--)
     if(choice[i]<n-r+i)
        break;
if(i<0)//已经是最后一个了
     return 0;
choice[i]+=1;
for(j=i+1;j<r;j++)
     choice[j]=choice[j-1]+1;
return 1;//还有后续组合
}

void comb(char source[],int total,int count)
{
int choice[5];
int i;
for(i=0;i<count;i++)//人工设置第一个组合
     choice[i]=i;
do
{
     printf("\n");
     for(i=0;i<count;i++)
     {//输出当前组合
         int t=choice[i];
          printf("%c ",source[t]);
     }
}while(fun(choice,count,total));
}

void main()
{
   char array[5]={'a','b','c','d','e'};
   clrscr();
   comb(array,5,2);
}

6. 组合应用:利用组合求解背包问题

     求n个元素的所有组合,有很多方法,有一个方式是这样的:定义一个参考数组并设初值为全0,然后处理参考数组(从后向前扫描,若当前元素值为1则变为0,继续扫描;若当前元素值为0则变为1,然后退出扫描)。接下来根据参考数组输出n个元素(若对应的参考数组中元素值为1,则选择此元素,即输出)。

       可以利用这个求组合的方法计算背包问题,其实背包问题本来就可以看作n个元素的r组合。代码如下:

#define K 10
#define N 10

i nclude <stdlib.h>
#i nclude <conio.h>

void create(long array[],int n,int k)
{
   int i,j;
   array[0]=1;
   for(i=1;i<n;i++)
   {
      long t=0;
      for(j=0;j<i;j++)
            t=t+array[j];
      array[i]=t+random(k)+1;
   }
}
void output(long array[],int n)
{
   int i;
   for(i=0;i<n;i++)
   {
      if(i%5==0)
           printf("\n");
      printf("%14ld",array[i]);
   }
}

void beibao(long array[],int cankao[],long value,int n)
{
   int i;
   int j;
   int cankao1[N]={0};
   long value1=0,value2=0;
   do
   {
      value2=0;
      for(j=n-1;j>=0;j--)/*处理参考数组*/
        if(cankao1[j]==0)
        {
          cankao1[j]=1;
          break;
         }
        else if(cankao1[j]==1)
              cankao1[j]=0;
      for(i=0;i<n;i++)/*计算当前选择方案*/
         if(cankao1[i]==1)
            value2+=array[i];
      if(value2<=value&&value2>value1)/*若当前选择方案可取*/
      {
         value1=value2;
          for(i=0;i<n;i++)/*记录选择方案*/
              cankao[i]=cankao1[i];
      }
   }while(j>=0);
}

void main()
{
   long array[N];
   int cankao[N]={0};
   int i;
   long value;
   clrscr();
   create(array,N,K);
   output(array,N);
   printf("\nInput the value of beibao:\n");
   scanf("%ld",&value);
   beibao(array,cankao,value,N);
   printf("\nThe answer is:\n");
   for(i=0;i<N;i++)
      if(cankao[i]==1)
       printf("%8ld",array[i]);
}

7. 组合应用:利用组合求解整数拆分

     所谓整数的拆分,就是把一个整数拆成多个整数之和,如

5=1+1+1+1+1

5=1+1+1+2

5=1+1+3

5=1+2+2

5=1+4

5=2+3

5=5

其实,整数拆分问题可以用过组合来求解,也就是在C(n,0),C(n,1),...,C(n,n)所有的组合中查找组合中所有数之和为n的组合。代码如下:

#i nclude <conio.h>

#define N 9
int r;
int result[20]={0};
zuhe(int m,int k)
{
   int i,j;
   for(i=m;i>=1;i--)
   {
      result[k-1]=i;
      if(k>1)
           zuhe(i,k-1);
      else
      {
         int temp=0;
           for(j=0;j<r;j++)
               temp+=result[j];
          if(temp==N)//当前组合是整数N的一个拆分
        {
            for(j=0;j<r;j++)
                printf("%4d",result[j]);
               printf("\n");
         }
      }
   }
}

main()
{
   clrscr();
   printf("\nN=%d\n",N);
   for(r=N;r>0;r--)
      zuhe(N,r);
}

运行结果:


N=7
   1   1   1   1   1   1   1
   1   1   1   1   1   2
   1   1   1   1   3
   1   1   1   2   2
   1   1   1   4
   1   1   2   3
   1   2   2   2
   1   1   5
   1   2   4
   1   3   3
   2   2   3
   1   6
   2   5
   3   4
   7
N=6
   1   1   1   1   1   1
   1   1   1   1   2
   1   1   1   3
   1   1   2   2
   1   1   4
   1   2   3
   2   2   2
   1   5
   2   4
   3   3
   6

N=5
   1   1   1   1   1
   1   1   1   2
   1   1   3
   1   2   2
   1   4
   2   3
   5
N=8
   1   1   1   1   1   1   1   1
   1   1   1   1   1   1   2
   1   1   1   1   1   3
   1   1   1   1   2   2
   1   1   1   1   4
   1   1   1   2   3
   1   1   2   2   2
   1   1   1   5
   1   1   2   4
   1   1   3   3
   1   2   2   3
   2   2   2   2
   1   1   6
   1   2   5
   1   3   4
   2   2   4
   2   3   3
   1   7
   2   6
   3   5
   4   4
   8

N=3
   1   1   1
   1   2
   3

posted @ 2012-02-14 20:54  _雨  阅读(777)  评论(0编辑  收藏  举报