zoukankan      html  css  js  c++  java
  • (转的)计算组合数——整数拆分

     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

  • 相关阅读:
    自从学会了搭建开源网站,妈妈再也不担心我找不到web自动化学习环境啦!
    领导要我6点下班前创建1000个有效的手机号,现在5点半了!教你用random模块10分钟搞定!
    python:字符串 扩展分片:第三个限制值
    python字符串:索引和分片
    测试基础:测试用例设计策略
    测试基础:软件测试思维方式
    面试整理:python列表面试习题
    面试整理:linux
    测试的一些思考
    python ConfigParse模块中的方法
  • 原文地址:https://www.cnblogs.com/0803yijia/p/2364029.html
Copyright © 2011-2022 走看看