Fastest way to generate binomial coefficients

后端 未结 11 1652
[愿得一人]
[愿得一人] 2020-12-13 02:20

I need to calculate combinations for a number.

What is the fastest way to calculate nCp where n>>p?

I need a fast way to generate binomial coefficients for a

相关标签:
11条回答
  • 2020-12-13 03:15

    If you want complete expansions for large values of n, FFT convolution might be the fastest way. In the case of a binomial expansion with equal coefficients (e.g. a series of fair coin tosses) and an even order (e.g. number of tosses) you can exploit symmetries thus:

    Theory

    Represent the results of two coin tosses (e.g. half the difference between the total number of heads and tails) with the expression A + A*cos(Pi*n/N). N is the number of samples in your buffer - a binomial expansion of even order O will have O+1 coefficients and require a buffer of N >= O/2 + 1 samples - n is the sample number being generated, and A is a scale factor that will usually be either 2 (for generating binomial coefficients) or 0.5 (for generating a binomial probability distribution).

    Notice that, in frequency, this expression resembles the binomial distribution of those two coin tosses - there are three symmetrical spikes at positions corresponding to the number (heads-tails)/2. Since modelling the overall probability distribution of independent events requires convolving their distributions, we want to convolve our expression in the frequency domain, which is equivalent to multiplication in the time domain.

    In other words, by raising our cosine expression for the result of two tosses to a power (e.g. to simulate 500 tosses, raise it to the power of 250 since it already represents a pair), we can arrange for the binomial distribution for a large number to appear in the frequency domain. Since this is all real and even, we can substitute the DCT-I for the DFT to improve efficiency.

    Algorithm

    1. decide on a buffer size, N, that is at least O/2 + 1 and can be conveniently DCTed
    2. initialise it with the expression pow(A + A*cos(Pi*n/N),O/2)
    3. apply the forward DCT-I
    4. read out the coefficients from the buffer - the first number is the central peak where heads=tails, and subsequent entries correspond to symmetrical pairs successively further from the centre

    Accuracy

    There's a limit to how high O can be before accumulated floating-point rounding errors rob you of accurate integer values for the coefficients, but I'd guess the number is pretty high. Double-precision floating-point can represent 53-bit integers with complete accuracy, and I'm going to ignore the rounding loss involved in the use of pow() because the generating expression will take place in FP registers, giving us an extra 11 bits of mantissa to absorb the rounding error on Intel platforms. So assuming we use a 1024-point DCT-I implemented via the FFT, that means losing 10 bits' accuracy to rounding error during the transform and not much else, leaving us with ~43 bits of clean representation. I don't know what order of binomial expansion generates coefficients of that size, but I dare say it's big enough for your needs.

    Asymmetrical expansions

    If you want the asymmetrical expansions for unequal coefficients of a and b, you'll need to use a two-sided (complex) DFT and a complex pow() function. Generate the expression A*A*e^(-Pi*i*n/N) + A*B + B*B*e^(+Pi*i*n/N) [using the complex pow() function to raise it to the power of half the expansion order] and DFT it. What you have in the buffer is, again, the central point (but not the maximum if A and B are very different) at offset zero, and it is followed by the upper half of the distribution. The upper half of the buffer will contain the lower half of the distribution, corresponding to heads-minus-tails values that are negative.

    Notice that the source data is Hermitian symmetrical (the second half of the input buffer is the complex conjugate of the first), so this algorithm is not optimal and can be performed using a complex-to-complex FFT of half the required size for optimum efficiency.

    Needless to say, all the complex exponentiation will chew more CPU time and hurt accuracy compared to the purely real algorithm for symmetrical distributions above.

    0 讨论(0)
  • 2020-12-13 03:15

    This is my version:

    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
    
    0 讨论(0)
  • 2020-12-13 03:17

    If I understand the notation in the question, you don't just want nCp, you actually want all of nC1, nC2, ... nC(n-1). If this is correct, we can leverage the following relationship to make this fairly trivial:

    • for all k>0: nCk = prod_{from i=1..k}( (n-i+1)/i )
    • i.e. for all k>0: nCk = nC(k-1) * (n-k+1) / k

    Here's a python snippet implementing this approach:

    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
    

    If you need all coefficients up to some k > ceiling(n/2), you can use symmetry to reduce the number of operations you need to perform by stopping at the coefficient for ceiling(n/2) and then just backfilling as far as you need.

    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
    
    0 讨论(0)
  • 2020-12-13 03:18

    The fastest reasonable approximation in my own benchmarking is the approximation used by the Apache Commons Maths library: http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/special/Gamma.html#logGamma(double)

    My colleagues and I tried to see if we could beat it, while using exact calculations rather than approximates. All approaches failed miserably (many orders slower) except one, which was 2-3 times slower. The best performing approach uses https://math.stackexchange.com/a/202559/123948, here is the code (in Scala):

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

    The really bad approaches where various attempts at implementing Pascal's Triangle using tail recursion.

    0 讨论(0)
  • 2020-12-13 03:18

    Time Complexity : O(denominator) Space Complexity : 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)); 
        } 
    
    }
    
    0 讨论(0)
提交回复
热议问题