web-dev-qa-db-fra.com

Moyen le plus rapide de générer des coefficients binomiaux

Je dois calculer des combinaisons pour un nombre.

Quel est le moyen le plus rapide de calculer nCp où n >> p?

J'ai besoin d'un moyen rapide pour générer des coefficients binomiaux pour une équation polynomiale et j'ai besoin d'obtenir le coefficient de tous les termes et de le stocker dans un tableau.

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

Quel est le moyen le plus efficace de calculer nCp?

24
Rajesh Pantula

Si vous souhaitez des extensions complètes pour des valeurs élevées de n, la convolution FFT peut constituer le moyen le plus rapide. Dans le cas d'une expansion binomiale avec des coefficients égaux (par exemple une série de lancers de pièces justes) et un ordre pair (par exemple le nombre de lancers), vous pouvez exploiter les symétries de la manière suivante:

Théorie

Représentez les résultats de deux lancers de pièces (par exemple, la moitié de la différence entre le nombre total de têtes et de queues) avec l'expression A + A * cos (Pi * n/N). N est le nombre d'échantillons dans votre tampon - une expansion binomiale d'ordre pair O aura O + 1 coefficients et nécessitera un tampon de N> = O/2 + 1 échantillons - n est le numéro de l'échantillon généré et A est un facteur d'échelle qui sera généralement soit 2 (pour générer des coefficients binomiaux) ou 0,5 (pour générer une distribution de probabilité binomiale).

Notez que, par sa fréquence, cette expression ressemble à la distribution binomiale de ces deux lancers de pièces: il y a trois pointes symétriques aux positions correspondant au nombre (têtes-queues)/2. Étant donné que la modélisation de la distribution de probabilité globale d’événements indépendants nécessite la convolution de leurs distributions, nous souhaitons convoluer notre expression dans le domaine fréquentiel, ce qui équivaut à la multiplication dans le domaine temporel.

En d’autres termes, en élevant notre expression cosinus pour le résultat de deux lancers en puissance (par exemple, pour simuler 500 lancers, augmentez-le à la puissance de 250 puisqu’il représente déjà une paire), nous pouvons organiser la distribution binomiale d’un grand numéro à afficher dans le domaine fréquentiel. Puisque tout cela est réel et même, nous pouvons substituer le DCT-I au DFT pour améliorer l'efficacité.

Algorithme

  1. choisir une taille de mémoire tampon, N, qui soit au moins égale à O/2 + 1 et pouvant être convenablement DCT
  2. l'initialiser avec l'expression pow (A + A * cos (Pi * n/N), O/2)
  3. appliquer le DCT-I avant
  4. lit les coefficients dans la mémoire tampon - le premier nombre est le pic central où head = tail, et les entrées suivantes correspondent à des paires symétriques successivement plus éloignées du centre

Précision

Il existe une limite à la valeur O pouvant être élevée avant que les erreurs d'arrondi en virgule flottante ne vous volent des valeurs entières précises pour les coefficients, mais je suppose que le nombre est assez élevé. La virgule flottante double précision peut représenter des nombres entiers de 53 bits avec une précision complète, et je vais ignorer la perte d’arrondi liée à l’utilisation de pow () car l’expression générée aura lieu dans des registres FP, ce qui donne Nous avons ajouté 11 bits supplémentaires de mantisse pour absorber l’erreur d’arrondi sur les plates-formes Intel. Supposons donc que nous utilisons un DCT-I à 1024 points implémenté via la FFT, ce qui signifie que nous perdons la précision de 10 bits au lieu d’erreur d’arrondi pendant la transformation et pas grand-chose, nous laissant ainsi environ 43 bits de représentation nette. Je ne sais pas quel ordre d'expansion binomiale génère des coefficients de cette taille, mais j'ose dire que c'est assez grand pour vos besoins.

Expansions asymétriques

Si vous voulez les extensions asymétriques pour les coefficients inégaux de a et b, vous devez utiliser une DFT à deux côtés (complexe) et une fonction pow () complexe. Générez l'expression A * A * e ^ (- Pi * i * n/N) + A * B + B * B * e ^ (+ Pi * i * n/N) [en utilisant la fonction complexe pow () pour augmenter à la puissance de la moitié de l'ordre d'expansion] et DFT. Ce que vous avez dans la mémoire tampon est, encore une fois, le point central (mais pas le maximum si A et B sont très différents) à l'offset zéro, suivi de la moitié supérieure de la distribution. La moitié supérieure de la mémoire tampon contiendra la moitié inférieure de la distribution, ce qui correspond aux valeurs head-moins-tails négatives.

Notez que les données source sont symétriques hermitiennes (la seconde moitié du tampon d’entrée est le conjugué complexe du premier), ainsi cet algorithme n’est pas optimal et peut être exécuté à l’aide d’une FFT complexe à complexe de la moitié de la taille requise Efficacité.

Inutile de dire que toutes les exponentiations complexes vont nous faire perdre plus de temps de calcul et de précision que l’algorithme purement réel pour les distributions symétriques ci-dessus.

8
tehsux0r

Vous pouvez utiliser la programmation dynamique pour générer des coefficients binomiaux

Vous pouvez créer un tableau et utiliser la boucle O (N ^ 2) pour le remplir

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

où 

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

Après cela, dans votre programme, vous pouvez obtenir la valeur C (n, k) en regardant simplement votre tableau 2D à [n, k] indices

UPDATEsmth comme ça

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];

où les N, K - valeurs maximales de votre n, k

15
Ribtoks

Si vous avez besoin de les calculer pour tout n, la réponse de Ribtoks est probablement la meilleure . Pour un seul n, mieux vaut procéder comme suit:

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

La division est exacte, si est faite après la multiplication. 

Et méfiez-vous des débordements de C [k] * (n-k): utilisez des entiers assez grands.

Ceci est ma 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
7
matcauthon

J'ai récemment écrit un morceau de code qui nécessitait un coefficient binaire d'environ 10 millions de fois. J'ai donc fait une approche combinant table de calcul/calcul qui ne gaspille toujours pas trop de mémoire. Vous pourriez le trouver utile (et mon code est dans le domaine public). Le code est à 

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

Il a été suggéré que j'inscrive le code ici. Une grande table de recherche qui kiffe semble être un gaspillage. Voici donc la dernière fonction et un script Python qui génère la table:

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 __== "__main__":
    sys.exit(main(sys.argv))
6
Lee Daniel Crocker

Si vous avez vraiment besoin que du cas où n est beaucoup plus grand que p, vous pouvez utiliser la formule de Stirling pour les factorielles. (si n >> 1 et p est l'ordre un, Stirling approximativement n! et (n-p) !, gardez p! tel quel, etc.)

5
ev-br

L’approximation raisonnable la plus rapide dans mes propres analyses comparatives est l’approximation utilisée par la bibliothèque Apache Commons Maths: http://commons.Apache.org/proper/commons-math/apidocs/org/Apache/commons/math3/special/Gamma .html # logGamma (double)

Mes collègues et moi avons essayé de voir si nous pouvions le battre, tout en utilisant des calculs exacts plutôt que des approximations. Toutes les approches ont lamentablement échoué (de nombreuses commandes plus lentement) sauf une, qui a été deux à trois fois plus lente. La méthode la plus performante utilise https://math.stackexchange.com/a/202559/123948 , voici le code (en Scala):

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

Les très mauvaises approches ont été diverses où diverses tentatives d’implémentation du triangle de Pascal ont été effectuées en utilisant la récursion de la queue.

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 )

Si nous élagions les mêmes termes du numérateur et du dénominateur, il nous reste une multiplication minimale requise. Nous pouvons écrire une fonction en C pour effectuer des multiplications 2p et une division pour obtenir 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

Je cherchais la même chose et je ne pouvais pas la trouver. J'en ai donc écrit une qui me semble optimale pour tout coeffcient binomial pour lequel le résultat final s'inscrit dans une longue.

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

Si je comprends la notation de la question, vous ne voulez pas seulement nCp, vous voulez en fait tout nC1, nC2, ... nC (n-1). Si cela est correct, nous pouvons tirer parti de la relation suivante pour rendre cela assez trivial:

  • pour tout k> 0: nCk = prod_ {de i = 1..k} ((n-i + 1)/i)
  • c'est-à-dire pour tout k> 0: nCk = nC (k-1) * (n-k + 1)/k

Voici un extrait de code Python implémentant cette approche:

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

Si vous avez besoin de tous les coefficients jusqu’à k> plafond (n/2), vous pouvez utiliser la symétrie pour réduire le nombre d’opérations à effectuer en arrêtant le coefficient pour plafond (n/2), puis en remplissant jusqu’à vous avez besoin. 

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