Эффективные подсчеты и перестановки

У меня есть код для подсчета перестановок и комбинаций, и я стараюсь, чтобы он работал лучше для больших чисел.

Я нашел лучший алгоритм для перестановок, который позволяет избежать больших промежуточных результатов, но я все же думаю, что я могу сделать лучше для комбинаций.

До сих пор я включил специальный случай, чтобы отразить симметрию nCr, но мне все же хотелось бы найти лучший алгоритм, который позволяет избежать вызова факториала (r), что является излишне большим промежуточным результатом. Без этой оптимизации последний доктрист слишком долго пытается вычислить факторный (99000).

Может ли кто-нибудь предложить более эффективный способ подсчета комбинаций?

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)

Ответ 1

если n не далеко от r, то использование рекурсивного определения комбинации, вероятно, лучше, так как xC0 == 1 у вас будет только несколько итераций:

Соответствующее рекурсивное определение здесь:

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

Это можно легко вычислить с помощью хвостовой рекурсии со следующим списком:

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

который, конечно, легко сгенерирован в Python (мы опускаем первую запись, так как nC0 = 1) на izip(xrange(n - r + 1, n+1), xrange(1, r+1)) Обратите внимание, что это предполагает, что r <= n вам нужно проверить это и заменить их, если они не являются. Также для оптимизации использования, если r < n/2, тогда r = n - r.

Теперь нам просто нужно применить шаг рекурсии, используя хвостовую рекурсию с уменьшением. Мы начинаем с 1, так как nC0 равно 1, а затем умножьте текущее значение на следующую запись из списка, как показано ниже.

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)

Ответ 2

Два довольно простых предложения:

  • Чтобы избежать переполнения, сделайте все в лог-пространстве. Используйте тот факт, что log (a * b) = log (a) + log (b) и log (a/b) = log (a) - log (b). Это упрощает работу с очень большими факториалами: log (n!/M!) = Log (n!) - log (m!) И т.д.

  • Используйте гамма-функцию вместо факториала. Вы можете найти его в scipy.stats.loggamma. Это гораздо более эффективный способ вычисления лог-факториалов, чем прямое суммирование. loggamma(n) == log(factorial(n - 1)), и аналогичным образом gamma(n) == factorial(n - 1).

Ответ 3

Если вам не требуется решение pure-python, gmpy2 может помочь (gmpy2.comb очень быстро).

Ответ 4

В scipy есть функция для этого, которая еще не упоминалась: scipy.special.comb. Это кажется эффективным на основе некоторых быстрых временных результатов для вашего doctest (~ 0,004 секунды для comb(100000, 1000, 1) == comb(100000, 99000, 1)).

[Хотя этот конкретный вопрос, похоже, касается алгоритмов, вопрос в том, что математическая функция ncr в python помечена как дубликат этого...]

Ответ 5

Если ваша проблема не требует знания точного количества перестановок или комбинаций, вы можете использовать приближение Стирлинга для факториала.

Это приведет к следующему коду:

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

Ответ 6

Если вы вычисляете N, выбираете K (это то, что я думаю, что вы делаете с ncr), есть динамическое программирующее решение, которое может быть намного быстрее. Это позволит избежать факториала, плюс вы можете сохранить таблицу, если хотите, для дальнейшего использования.

Вот учебная ссылка для него:

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

Я не уверен, как лучше решить вашу первую проблему, хотя, извините.

Изменить: Вот макет. Есть некоторые довольно веселые ошибки, которые могут быть заодно, поэтому он может, безусловно, выдержать немного больше.

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]

Ответ 7

from scipy import misc
misc.comb(n, k)

должен позволять вам подсчитывать комбинации

Ответ 8

Более эффективное решение для nCr - пространство мудрое и точное.

Посредник (res) гарантированно всегда будет int и никогда не будет больше результата. Сложность пространства - O (1) (без списков, без застежек, без стека), сложность по времени - O (r) - ровно r умножений и r делений.

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

Ответ 9

Использование xrange() вместо range() немного ускорит процесс из-за того, что промежуточный список не создается, не заполняется, не повторяется и не уничтожается. Кроме того, reduce() с operator.mul.

Ответ 10

Для N выберите K, вы можете использовать треугольник Паскалей. В основном вам нужно будет хранить массив размера N для вычисления всех N значений K. Требуются только дополнения.

Ответ 11

Вы можете ввести два целых числа и импортировать математическую библиотеку, чтобы найти факториал, а затем применить формулу nCr

import math
n,r=[int(_)for _ in raw_input().split()]
f=math.factorial
print f(n)/f(r)/f(n-r)

Ответ 12

from numpy import prod

def nCr(n,r):
    numerator = range(n, max(n-r,r),-1)
    denominator = range(1, min(n-r,r) +1,1)
    return int(prod(numerator)/prod(denominator))