Compter efficacement les combinaisons et les permutations

j'ai du code pour compter les permutations et les combinaisons, et j'essaie de le faire fonctionner mieux pour les grands nombres.

j'ai trouvé un meilleur algorithme pour les permutations qui évite les grands résultats intermédiaires, mais je pense toujours que je peux faire mieux pour les combinaisons.

Jusqu'à présent, j'ai mis dans un cas spécial pour refléter la symétrie de nCr, mais je voudrais toujours trouver un meilleur algorithme qui évite l'appel à factoriel(r), qui est un inutilement grand résultat intermédiaire. Sans cette optimisation, le dernier doctest prend trop de temps à essayer de calculer factoriel(99000).

quelqu'un Peut-il suggérer un moyen plus efficace de compter les combinaisons?

from math import factorial

def product(iterable):
    prod = 1
    for n in iterable:
        prod *= n
    return prod

def npr(n, r):
    """
    Calculate the number of ordered permutations of r items taken from a
    population of size n.

    >>> npr(3, 2)
    6
    >>> npr(100, 20)
    1303995018204712451095685346159820800000
    """
    assert 0 <= r <= n
    return product(range(n - r + 1, n + 1))

def ncr(n, r):
    """
    Calculate the number of unordered combinations of r items taken from a
    population of size n.

    >>> ncr(3, 2)
    3
    >>> ncr(100, 20)
    535983370403809682970
    >>> ncr(100000, 1000) == ncr(100000, 99000)
    True
    """
    assert 0 <= r <= n
    if r > n // 2:
        r = n - r
    return npr(n, r) // factorial(r)
31
demandé sur Christian Oudard 2010-01-19 22:52:03

11 réponses

si n n'est pas loin de r, alors l'utilisation de la définition récursive de la combinaison est probablement préférable, puisque xC0 == 1 vous n'aurez que quelques itérations:

la définition récursive pertinente ici est:

nCr = (n-1) C(r-1) * n / r

cela peut être bien calculé en utilisant la récursion de la queue avec la liste suivante:

[(n - r, 0), (n - r + 1, 1), (n - r + 2, 2), ..., (n - 1, r - 1), (n, r)]

qui est bien sûr facilement généré en Python (nous omettons la première entrée puisque nC0 = 1) par izip(xrange(n - r + 1, n+1), xrange(1, r+1)) notez que cela suppose que r <= n Vous devez vérifier cela et les échanger s'ils ne le sont pas. Aussi pour optimiser l'utilisation si r < n / 2 puis r = n-R.

maintenant nous avons simplement besoin d'appliquer l'étape de recursion en utilisant la recursion de queue avec réduire. Nous commençons avec 1 puisque nC0 est 1 et ensuite nous multiplions la valeur courante avec la prochaine entrée de la liste comme ci-dessous.

from itertools import izip

reduce(lambda x, y: x * y[0] / y[1], izip(xrange(n - r + 1, n+1), xrange(1, r+1)), 1)
22
répondu wich 2010-01-19 21:12:01

deux suggestions assez simples:

  1. pour éviter les débordements, faites tout dans l'espace de log. Utiliser le fait que log(a * b) = log(a) + log(b) et log(a / b) = log(a) - log(b). Cela rend facile de travailler avec de très grands factoriels: log (n! / m!) = log(n!)- log(m!), etc.

  2. utilisez la fonction gamma au lieu de factorielle. Vous pouvez en trouver un dans scipy.stats.loggamma . C'est une façon beaucoup plus efficace de calculer les facteurs logarithmiques plutôt que la sommation directe. loggamma(n) == log(factorial(n - 1)) , et de même, gamma(n) == factorial(n - 1) .

16
répondu dsimcha 2010-01-20 15:27:45

si vous n'avez pas besoin d'une solution de python pur, gmpy2 pourrait vous aider ( gmpy2.comb est très rapide).

6
répondu Alex Martelli 2016-02-27 21:37:00

il y a une fonction pour ceci en scipy qui n'a pas encore été mentionnée: scipy.spécial.peigne . Il semble efficace basé sur quelques résultats de chronométrage rapide pour votre doctest (~0.004 secondes pour comb(100000, 1000, 1) == comb(100000, 99000, 1) ).

[alors que cette question spécifique semble être au sujet des algorithmes la question est-il une fonction ncr de mathématiques en python est marquée comme un duplicata de ceci...]

6
répondu dshepherd 2018-02-15 16:41:34

si votre problème ne nécessite pas de connaître le nombre exact de permutations ou de combinaisons, alors vous pouvez utiliser approximation de Stirling pour le factoriel.

qui conduirait à un code comme celui-ci:

import math

def stirling(n):
    # http://en.wikipedia.org/wiki/Stirling%27s_approximation
    return math.sqrt(2*math.pi*n)*(n/math.e)**n

def npr(n,r):
    return (stirling(n)/stirling(n-r) if n>20 else
            math.factorial(n)/math.factorial(n-r))

def ncr(n,r):    
    return (stirling(n)/stirling(r)/stirling(n-r) if n>20 else
            math.factorial(n)/math.factorial(r)/math.factorial(n-r))

print(npr(3,2))
# 6
print(npr(100,20))
# 1.30426670868e+39
print(ncr(3,2))
# 3
print(ncr(100,20))
# 5.38333246453e+20
3
répondu unutbu 2010-01-19 20:58:39

si vous calculez N choose K (ce que je pense que vous faites avec ncr), il y a une solution de programmation dynamique qui peut être beaucoup plus rapide. Cela évitera les factoriels, en plus vous pouvez garder la table si vous voulez pour une utilisation ultérieure.

voici un lien d'enseignement pour cela:

http://www.csc.liv.ac.uk/~ped/teachadmin/algor/dyprog.html

Je ne sais pas comment mieux résoudre votre premier problème, si, désolé.

Edit: Voici la maquette. Il y a quelques assez hilarant tout-en-un d'erreurs, de sorte qu'il peut certainement être plus propre.

import sys
n = int(sys.argv[1])+2#100
k = int(sys.argv[2])+1#20
table = [[0]*(n+2)]*(n+2)

for i in range(1,n):
    table[i][i] = 1
for i in range(1,n):
    for j in range(1,n-i):
        x = i+j
        if j == 1: table[x][j] = 1
        else: table[x][j] = table[x-1][j-1] + table[x-1][j]

print table[n][k]
2
répondu agorenst 2010-01-19 21:42:10
from scipy import misc
misc.comb(n, k)

devrait vous permettre de compter les combinaisons

1
répondu Divyansh 2017-02-06 04:59:57

solution plus efficace pour nCr - espace sage et précision Sage.

l'intermédiaire (res) est garanti d'être toujours int et jamais plus grand que le résultat. La complexité de l'espace est O(1) (Pas de liste, pas de fermeture éclair, pas de pile), la complexité du temps est O (r) - exactement R multiplications et R divisions.

def ncr(n, r):
    r = min(r, n-r)
    if r == 0: return 1
    res = 1
    for k in range(1,r+1):
        res = res*(n-k+1)/k
    return res
1
répondu ZXX 2017-06-17 02:35:22

en utilisant xrange() au lieu de range() accélérera légèrement les choses en raison du fait qu'aucune liste intermédiaire n'est créée, peuplée, itérée et ensuite détruite. Aussi, reduce() avec operator.mul .

0
répondu Ignacio Vazquez-Abrams 2010-01-19 20:14:32

pour n choisir K vous pourriez utiliser le triangle Pascals. Fondamentalement, vous auriez besoin de garder le tableau de la taille N autour pour calculer toutes les valeurs de n choisir K. Seulement des ajouts seraient nécessaires.

0
répondu Richie 2010-01-19 20:47:58

vous pouvez entrer deux entiers et importer bibliothèque de mathématiques pour trouver le factoriel, puis appliquer la formule nCr

import math
n,r=[int(_)for _ in raw_input().split()]
f=math.factorial
print f(n)/f(r)/f(n-r)
0
répondu Gaurav Jain 2016-08-29 00:30:36