web-dev-qa-db-ja.com

二項係数を生成する最速の方法

数値の組み合わせを計算する必要があります。

N >> pでnCpを計算する最も速い方法は何ですか?

多項式の二項係数をすばやく生成する方法が必要です。すべての項の係数を取得して配列に格納する必要があります。

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

NCpを計算する最も効率的な方法は何ですか?

25
Rajesh Pantula

Nの大きな値に対して完全な拡張が必要な​​場合は、FFT畳み込みが最も高速な方法である可能性があります。係数が等しい(一連の公正なコインのトスなど)および偶数次数(たとえば、トスの数)の二項展開の場合、対称性を利用できます。

理論

A + A * cos(Pi * n/N)という式で、2回のコイントスの結果(表と裏の総数の差の半分など)を表します。 Nはバッファー内のサンプル数です。偶数次数Oの二項展開にはO + 1係数があり、N> = O/2 + 1サンプルのバッファーが必要です-nは生成されるサンプル数、Aはaです。通常は2(二項係数を生成する場合)または0.5(二項確率分布を生成する場合)のいずれかになるスケール係数。

頻度において、この式はそれらの2つのコイントスの二項分布に似ていることに注意してください。(heads-tails)/ 2の数に対応する位置に3つの対称スパイクがあります。独立したイベントの全体的な確率分布をモデル化するには、それらの分布をたたみ込む必要があるため、周波数領域で式をたたみ込みます。これは、時間領域での乗算に相当します。

言い換えると、2回のトスの結果の余弦表現を累乗する(たとえば、500回のトスをシミュレートするには、すでにペアを表しているので250の累乗に上げる)ことにより、大きな二項分布を調整できます。周波数領域に表示される番号。これはすべて現実的であり、DFTの代わりにDCT-Iを使用して効率を向上させることができます。

アルゴリズム

  1. 少なくともO/2 + 1であり、便利にDCTできるバッファーサイズNを決定する
  2. 式pow(A + A * cos(Pi * n/N)、O/2)で初期化します
  3. フォワードDCT-Iを適用する
  4. バッファから係数を読み取ります-最初の数は中央のピークで、heads = tailsで、後続のエントリは対称的なペアに対応し、中央から連続的に離れています

精度

蓄積された浮動小数点の丸め誤差が係数の正確な整数値を奪う前にOがどれほど高くなるかには限界がありますが、その数はかなり高いと思います。倍精度浮動小数点は、完全な精度で53ビット整数を表すことができます。式の生成はFPレジスターで行われるため、pow()の使用に伴う丸め損失は無視します。 、Intelプラットフォームでの丸め誤差を吸収するために、仮数の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しますバッファにあるのは、やはり、オフセット0の中心点(ただし、AとBが大きく異なる場合の最大値ではありません)であり、その後に分布の上半分が続きます。バッファの上半分には分布の下半分が含まれ、負の値であるheads-minus-tails値に対応します。

ソースデータはエルミート対称であることに注意してください(入力バッファーの後半は最初のバッファーの複素共役です)。このアルゴリズムは最適ではなく、最適に必要なサイズの半分の複素数から複素数のFFTを使用して実行できます。効率。

言うまでもなく、上記の対称的な分布の純粋に実際のアルゴリズムと比較して、すべての複雑な累乗は、より多くのCPU時間を消費し、精度を損ないます。

8
tehsux0r

二項係数を生成するために動的プログラミングを使用できます

あなたは配列を作成し、それを埋めるためにO(N ^ 2)ループを使用することができます

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

どこ

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

その後、プログラムで、[n、k]インデックスで2D配列を見るだけでC(n、k)値を取得できます

[〜#〜] update [〜#〜]そのようなsmth

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の最大値

15
Ribtoks

すべてのnについてそれらを計算する必要がある場合、Ribtoksの答えがおそらく最良です。単一のnの場合は、次のようにするのがよいでしょう。

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

除算は正確ですifは乗算の後に行われます。

また、C [k] *(n-k)によるオーバーフローに注意してください。十分に大きな整数を使用してください。

これは私のバージョンです:

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
7
matcauthon

最近、バイナリ係数を約1000万回呼び出す必要があるコードを書きました。それで、私はルックアップテーブルと計算の組み合わせのアプローチを行いましたが、それでもメモリの浪費にはなりません。あなたはそれが便利だと思うかもしれません(そして私のコードはパブリックドメインにあります)。コードは

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

ここにコードをインライン化することをお勧めします。大きなhonkingルックアップテーブルは無駄のように思えるので、最後の関数と、テーブルを生成する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))
7

Nがpよりはるかに大きい場合だけが本当に必要な場合、1つの方法は、階乗に スターリングの公式 を使用することです。 (n >> 1かつpが次数1の場合、スターリング近似n!および(n-p)!、 p!はそのままにするなど)

5
ev-br

私自身のベンチマークで最も速い妥当な近似は、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のトライアングルを実装するさまざまな試みが行われている本当に悪いアプローチ。

4
samthebest
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;
}
2
mitin001

私は同じものを探していて、それを見つけることができなかったので、結果がLongに適合する任意の二項係数に最適であると思われるものを自分で書きました。

// 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;
}
1
Jeroen Vuurens

時間の複雑さ:O(denominator)空間の複雑さ: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

質問の表記を理解していれば、nCpだけではなく、nC1、nC2、... nC(n-1)のすべてが実際に必要です。これが正しければ、次の関係を活用してこれをかなり簡単にすることができます。

  • すべてのk> 0の場合:nCk = prod_ {from 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> ceiling(n/2)までのすべての係数が必要な場合、対称性を使用して、ceiling(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
0
David Marx