zoukankan      html  css  js  c++  java
  • 『转』组合数快速算法!!!

      计算组合数最大的困难在于数据的溢出,对于大于150的整数n求阶乘很容易超出double类型的范围,那么当C(n,m)中的n=200时,直接用组合公式计算基本就无望了。另外一个难点就是效率。

        对于第一个数据溢出的问题,可以这样解决。因为组合数公式为:
        C(n,m) = n!/(m!(n-m)!)

        为了避免直接计算n的阶乘,对公式两边取对数,于是得到:
        ln(C(n,m)) = ln(n!)-ln(m!)-ln((n-m)!)
      

     

        进一步化简得到:

     这样我们就把连乘转换为了连加,因为ln(n)总是很小的,所以上式很难出现数据溢出。

        为了解决第二个效率的问题,我们对上式再做一步化简。上式已经把连乘法变成了求和的线性运算,也就是说,上式已经极大地简化了计算的复杂度,但是还可以进一步优化。从上式中,我们很容易看出右边的3项必然存在重复的部分。现在我们把右边第一项拆成两部分:

    这样,上式右边第一项就可以被抵消掉,于是得到:

    上式直接减少了2m次对数计算及求和运算。但是这个公式还可以优化。对于上面公式里的求和,当mn/2时,n-m就会小很多。我们知道:
        C(n,m) = C(n,n-m)

        那么通过这个公式,我们可以把小于n/2的m变为大于n/2的n-m再进行计算,结果是一样的,但是却能减少计算量。

        当计算出ln(C(n,m))后,只需要取自然对数,就可以得到组合数:
        C(n,m) = exp(ln(C(n,m)))

        这样就完成了组合数的计算。
        用这种方法计算组合数,如果只计算ln(C(n,m))的话,n可以取到整型数据的极限值65535,
        ln(C(65535,32767)) = 45419.6

        而计算时间只需要0.01ms。当然,如果要取对数得到最终的组合数的话,n的取值就不能达到这么大了。但是这种算法仍然可以保证n取到1000以上,而不是开头说的150这个极限值。例如:
        C(1000,500) = 2.70288e+299
        计算时间仍然小于0.01ms。

        采用我这种算法,不仅n的取值范围大,而且计算速度高,不像用递归算法实现这个问题的时候,很容易陷入递归层次太深而导致计算时间太长。

        算法代码实现如下:

    1 double lnchoose(int n, int m)
         2 {
         3     if (m > n)
         4     {
         5         return 0;
         6     }
         7     if (m < n/2.0)
         8     {
         9         m = n-m;
        10     }
        11     double s1 = 0;
        12     for (int i=m+1; i<=n; i++)
        13     {
        14         s1 += log((double)i);
        15     }
        16     double s2 = 0;
        17     int ub = n-m;
        18     for (int i=2; i<=ub; i++)
        19     {
        20         s2 += log((double)i);
        21     }
        22     return s1-s2;
        23 }
        24
        25 double choose(int n, int m)
        26 {
        27     if (m > n)
        28     {
        29         return 0;
        30     }
        31     return exp(lnchoose(n, m));
        32 }

    //C(N,M)=C(N-1,M-1)+C(N-1,M)
     
     1 #include <iostream>
     2 #include <cmath>
     3 
     4 using namespace std;
     5 
     6 double c(int m,int n)
     7 {
     8     double sa,sb,sc;
     9     sa=sb=sc=0.0;
    10     for(int i=2;i<=m;i++)
    11     {
    12         sa+=log((double)i);
    13     }
    14     for(int i=2;i<=n;i++)
    15     {
    16         sb+=log((double)i);
    17     }
    18     for(int i=2;i<=n-m;i++)
    19     {
    20         sc+=log((double)i);
    21     }
    22 
    23    //cout<<sa<<" "<<sb<<" "<<sc<<endl;
    24 
    25     return sb-sa-sc;
    26 }
    27 
    28 int main()
    29 {
    30     int m,n;
    31     cin>>m>>n;
    32     cout<<exp(c(m,n));
    33     return 0;
    34 }
  • 相关阅读:
    搭建php环境时解决jpeg6 make: ./libtool:命令未找到
    configure: error: zlib not installed
    南京呼叫中心防火墙配置(备份)
    检查Linux Bash安全漏洞以及各环境修复解决方法
    Linux 内核升级步骤
    CentOS 7没有ifconfig命令处理
    linux kickstart 自动安装
    red hat Linux 使用CentOS yum源更新
    -bash: ./job.sh: /bin/sh^M: bad interpreter: 没有那个文件或目录
    linux LNMP自动安装脚本
  • 原文地址:https://www.cnblogs.com/CKboss/p/3015010.html
Copyright © 2011-2022 走看看