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)
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)
deux suggestions assez simples:
-
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.
-
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)
.
si vous n'avez pas besoin d'une solution de python pur, gmpy2 pourrait vous aider ( gmpy2.comb
est très rapide).
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...]
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
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]
from scipy import misc
misc.comb(n, k)
devrait vous permettre de compter les combinaisons
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
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
.
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.
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)