生成二项式系数的最快方法

时间:2012-06-14 12:10:23

标签: algorithm math data-structures

我需要计算一个数字的组合。

计算nCp的最快方法是什么,其中n>> p?

我需要一种快速的方法来为多项式方程生成二项式系数,我需要得到所有项的系数并将其存储在数组中。

  

(a + b)^ n = a ^ n + nC1 a ^(n-1)* b + nC2 a ^(n-2)* ............   + nC(n-1)a * b ^(n-1)+ b ^ n

计算nCp的最有效方法是什么?

11 个答案:

答案 0 :(得分:15)

您需要使用动态编程来生成二项式系数

您可以创建一个数组,然后使用 O(N ^ 2)循环来填充它

C[n, k] = C[n-1, k-1] + C[n-1, k];

其中

C[1, 1] = C[n, n] = 1

在你的程序中,你可以得到C(n,k)值只是在[n,k]索引处查看你的2D数组

UPDATE 就像那样

for (int k = 1; k <= K; k++) C[0][k] = 0;
for (int n = 0; n <= N; n++) C[n][0] = 1;

for (int n = 1; n <= N; n++)
   for (int k = 1; k <= K; k++)
      C[n][k] = C[n-1][k-1] + C[n-1][k];

其中N,K - 你的n,k的最大值

答案 1 :(得分:13)

如果你需要为所有n计算它们,Ribtoks的答案可能是最好的。 对于单个n,你最好这样做:

C[0] = 1
for (int k = 0; k < n; ++ k)
    C[k+1] = (C[k] * (n-k)) / (k+1)

除法是精确的,如果在乘法后完成。

请注意溢出C [k] *(n-k):使用足够大的整数。

答案 2 :(得分:8)

如果想要对n的大值进行完全展开,则FFT卷积可能是最快的方法。在具有相等系数的二项式展开(例如一系列公平的硬币投掷)和偶数顺序(例如投掷数量)的情况下,您可以利用对称性:

<强>理论

用表达式A + A * cos(Pi * n / N)表示两次硬币投掷的结果(例如,头部和尾部总数之差的一半)。 N是缓冲区中的样本数 - 偶数阶O的二项式扩展将具有O + 1个系数,并且需要N> = O / 2 + 1个样本的缓冲区 - n是生成的样本编号,A是比例因子通常为2(用于生成二项式系数)或0.5(用于生成二项式概率分布)。

请注意,在频率上,此表达式类似于这两个硬币投掷的二项分布 - 在与数字(头尾)/ 2对应的位置处有三个对称尖峰。由于对独立事件的整体概率分布进行建模需要对其分布进行卷积,我们希望在频域中对我们的表达进行卷积,这相当于时域中的乘法。

换句话说,通过将两次投掷的结果的余弦表达式提高到一个幂(例如,模拟500次投掷,将其提升到250的幂,因为它已经代表一对),我们可以安排二项分布大数出现在频域中。由于这一切都是真实的,我们可以用DCT-I代替DFT来提高效率。

<强>算法

  1. 决定缓冲区大小N,至少为O / 2 + 1,可以方便地进行DCTed
  2. 用表达式pow(A + A * cos(Pi * n / N),O / 2)初始化它
  3. 应用前向DCT-I
  4. 从缓冲区读出系数 - 第一个数字是head = tails的中心峰值,后续条目对应于连续离中心的对称对
  5. <强>精度

    在积累的浮点舍入误差为你提供系数的精确整数值之前,O的高度有限,但我猜这个数字非常高。双精度浮点可以完全准确地表示53位整数,我将忽略使用pow()时所涉及的舍入损失,因为生成表达式将在FP寄存器中发生,给我们额外的11尾数位用于吸收英特尔平台上的舍入误差。因此,假设我们使用通过FFT实现的1024点DCT-I,这意味着在变换期间丢失10位的精度以舍入误差,而不是其他多少,留下约43位的清晰表示。我不知道二项式扩展的顺序会产生那么大的系数,但我敢说它足以满足你的需求。

    非对称扩展

    如果你想要a和b的不等系数的非对称展开,你需要使用双面(复杂)DFT和复杂的pow()函数。生成表达式A * A * e ^( - Pi * i * n / N)+ A * B + B * B * e ^(+ Pi * i * n / N)[使用复杂的pow()函数来提高它是扩展顺序的一半]和DFT它。你在缓冲区中所拥有的是偏移零点处的中心点(但是如果A和B非常不同则不是最大值),然后是分布的上半部分。缓冲区的上半部分将包含分布的下半部分,对应于负数的head-minus-tails值。

    请注意,源数据是Hermitian对称的(输入缓冲区的后半部分是第一个的复共轭),因此该算法不是最优的,可以使用复杂到复数的FFT执行一半所需的尺寸以获得最佳效率。

    毋庸置疑,与上面对称分布的纯实数算法相比,所有复指数都会咀嚼更多的CPU时间并损害精度。

答案 3 :(得分:7)

这是我的版本:

def binomial(n, k):
if k == 0:
    return 1
elif 2*k > n:
    return binomial(n,n-k)
else:
    e = n-k+1
    for i in range(2,k+1):
        e *= (n-k+i)
        e /= i
    return e

答案 4 :(得分:7)

我最近编写了一段需要调用二进制系数大约1000万次的代码。所以我做了一个组合查找表/计算方法,仍然没有太浪费内存。您可能会发现它很有用(我的代码在公共域中)。代码在

http://www.etceterology.com/fast-binomial-coefficients

有人建议我在这里内联代码。一个大的鸣喇叭查找表似乎是浪费,所以这是最终的函数,以及生成表的Python脚本:

extern long long bctable[]; /* See below */

long long binomial(int n, int k) {
    int i;
    long long b;
    assert(n >= 0 && k >= 0);

    if (0 == k || n == k) return 1LL;
    if (k > n) return 0LL;

    if (k > (n - k)) k = n - k;
    if (1 == k) return (long long)n;

    if (n <= 54 && k <= 54) {
        return bctable[(((n - 3) * (n - 3)) >> 2) + (k - 2)];
    }
    /* Last resort: actually calculate */
    b = 1LL;
    for (i = 1; i <= k; ++i) {
        b *= (n - (k - i));
        if (b < 0) return -1LL; /* Overflow */
        b /= i;
    }
    return b;
}

#!/usr/bin/env python3

import sys

class App(object):
    def __init__(self, max):
        self.table = [[0 for k in range(max + 1)] for n in range(max + 1)]
        self.max = max

    def build(self):
        for n in range(self.max + 1):
            for k in range(self.max + 1):
                if k == 0:      b = 1
                elif  k > n:    b = 0
                elif k == n:    b = 1
                elif k == 1:    b = n
                elif k > n-k:   b = self.table[n][n-k]
                else:
                    b = self.table[n-1][k] + self.table[n-1][k-1]
                self.table[n][k] = b

    def output(self, val):
        if val > 2**63: val = -1
        text = " {0}LL,".format(val)

        if self.column + len(text) > 76:
            print("\n   ", end = "")
            self.column = 3
        print(text, end = "")
        self.column += len(text)

    def dump(self):
        count = 0
        print("long long bctable[] = {", end="");

        self.column = 999
        for n in range(self.max + 1):
            for k in range(self.max + 1):
                if n < 4 or k < 2 or k > n-k:
                    continue
                self.output(self.table[n][k])
                count += 1
        print("\n}}; /* {0} Entries */".format(count));

    def run(self):
        self.build()
        self.dump()
        return 0

def main(args):
    return App(54).run()

if __name__ == "__main__":
    sys.exit(main(sys.argv))

答案 5 :(得分:5)

如果你真的只需要n大于p的情况,一种方法是使用Stirling's formula作为阶乘。 (如果n>&gt;&gt; 1且p是1阶,Stirling接近n!和(n-p)!,保持p!原样等。)

答案 6 :(得分:4)

我自己的基准测试中最快的合理近似值是Apache Commons Maths库使用的近似值:http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/special/Gamma.html#logGamma(double)

我和我的同事试图看看我们是否可以击败它,同时使用精确的计算而非近似。所有方法都失败了(许多命令较慢),除了一个,慢了2-3倍。表现最佳的方法使用https://math.stackexchange.com/a/202559/123948,这是代码(在Scala中):

var i: Int = 0
var binCoeff: Double = 1
while (i < k) {
  binCoeff *= (n - i) / (k - i).toDouble
  i += 1
}
binCoeff

使用尾递归实现Pascal三角形的各种尝试的非常糟糕的方法。

答案 7 :(得分:2)

nCp = n! / ( p! (n-p)! ) =
      ( n * (n-1) * (n-2) * ... * (n - p) * (n - p - 1) * ... * 1 ) /
      ( p * (p-1) * ... * 1     * (n - p) * (n - p - 1) * ... * 1 )

如果我们修剪分子和分母的相同项,我们只需要最小的乘法。我们可以在C中写一个函数来执行2p乘法和1个除法来得到nCp:

int binom ( int p, int n ) {
    if ( p == 0 ) return 1;
    int num = n;
    int den = p;
    while ( p > 1 ) {
        p--;
        num *= n - p;
        den *= p;
    }
    return num / den;
}

答案 8 :(得分:1)

我一直在寻找相同的东西而找不到它,所以我自己写了一篇对于任何二项式系数来说都是最优的,而这种二项式系数对于它来说是最好的。

// Calculate Binomial Coefficient 
// Jeroen B.P. Vuurens
public static long binomialCoefficient(int n, int k) {
    // take the lowest possible k to reduce computing using: n over k = n over (n-k)
    k = java.lang.Math.min( k, n - k );

    // holds the high number: fi. (1000 over 990) holds 991..1000
    long highnumber[] = new long[k];
    for (int i = 0; i < k; i++)
        highnumber[i] = n - i; // the high number first order is important
    // holds the dividers: fi. (1000 over 990) holds 2..10
    int dividers[] = new int[k - 1];
    for (int i = 0; i < k - 1; i++)
        dividers[i] = k - i;

    // for every dividers there is always exists a highnumber that can be divided by 
    // this, the number of highnumbers being a sequence that equals the number of 
    // dividers. Thus, the only trick needed is to divide in reverse order, so 
    // divide the highest divider first trying it on the highest highnumber first. 
    // That way you do not need to do any tricks with primes.
    for (int divider: dividers) {
       boolean eliminated = false;
       for (int i = 0; i < k; i++) {
          if (highnumber[i] % divider == 0) {
             highnumber[i] /= divider;
             eliminated = true;
             break;
          }
       }
       if(!eliminated) throw new Error(n+","+k+" divider="+divider);
    }


    // multiply remainder of highnumbers
    long result = 1;
    for (long high : highnumber)
       result *= high;
    return result;
}

答案 9 :(得分:0)

如果我理解问题中的符号,你不仅仅想要nCp,你实际上想要所有的nC1,nC2,...... nC(n-1)。如果这是正确的,我们可以利用以下关系来实现这一点:

  • 对于所有k&gt; 0:nCk = prod_ {来自i = 1..k}((n-i + 1)/ i)
  • 即。对于所有k> 0:nCk = nC(k-1)*(n-k + 1)/ k

这是一个实现这种方法的python片段:

def binomial_coef_seq(n, k):
    """Returns a list of all binomial terms from choose(n,0) up to choose(n,k)"""
    b = [1]
    for i in range(1,k+1):
        b.append(b[-1] * (n-i+1)/i)
    return b

如果你需要所有系数高达某些k>天花板(n / 2),您可以使用对称性来减少需要执行的操作次数,方法是停止上限系数(n / 2),然后根据需要进行回填。

import numpy as np

def binomial_coef_seq2(n, k):
    """Returns a list of all binomial terms from choose(n,0) up to choose(n,k)"""

    k2 = int(np.ceiling(n/2))

    use_symmetry =  k > k2
    if use_symmetry:
        k = k2

    b = [1]
    for i in range(1, k+1):
        b.append(b[-1] * (n-i+1)/i)

    if use_symmetry:
        v = k2 - (n-k)
        b2 = b[-v:]
        b.extend(b2)
    return b

答案 10 :(得分:0)

时间复杂度:O(分母) 空间复杂度:O(1)

public class binomialCoeff {
    static double binomialcoeff(int numerator, int denominator) 
    { 
        double res = 1; 
        //invalid numbers
        if (denominator>numerator || denominator<0 || numerator<0) {
            res = -1;
            return res;}
        //default values
        if(denominator==numerator || denominator==0 || numerator==0)
            return res;


        // Since C(n, k) = C(n, n-k) 
        if ( denominator > (numerator - denominator) ) 
            denominator = numerator - denominator;


        // Calculate value of [n * (n-1) *---* (n-k+1)] / [k * (k-1) *----* 1] 
        while (denominator>=1)
        { 

        res *= numerator;
        res = res / denominator; 

        denominator--;
        numerator--;
        } 

        return res; 
    } 

    /* Driver program to test above function*/
    public static void main(String[] args) 
    { 
        int numerator = 120; 
        int denominator = 20; 
        System.out.println("Value of C("+ numerator + ", " + denominator+ ") "
                                + "is" + " "+ binomialcoeff(numerator, denominator)); 
    } 

}