web-dev-qa-db-ja.com

組み合わせと順列を効率的にカウントする

私は順列と組み合わせを数えるコードをいくつか持っています、そして私はそれを大きな数でよりうまく機能させるようにしています。

大きな中間結果を回避する順列のためのより良いアルゴリズムを見つけましたが、それでも私は組み合わせに対してより良いことができると思います。

ここまでは、nCrの対称性を反映するために特別なケースを用意しましたが、不必要に大きな中間結果であるfactorial(r)の呼び出しを回避するより良いアルゴリズムを見つけたいと思っています。この最適化を行わないと、最後のdoctestがfactorial(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)
35

nがrから遠くない場合、xC0 == 1であるため、反復の組み合わせの再帰的な定義を使用する方がおそらく良いでしょう。

ここでの関連する再帰的な定義は次のとおりです。

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

これは、次のリストの末尾再帰を使用してうまく計算できます。

[(n-r、0)、(n-r + 1、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/2の場合に使用を最適化するには、r = n-rを使用します。

ここで、reduceを使用したテール再帰を使用して、再帰ステップを適用するだけです。 nC0が1であるため、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)
26
wich

2つのかなり単純な提案:

  1. オーバーフローを回避するには、すべてをログスペースで実行します。 log(a * b)= log(a)+ log(b)、およびlog(a/b)= log(a)-log(b)という事実を使用します。これにより、非常に大きな階乗での作業が簡単になります:log(n!/ m!)= log(n!)-log(m!)など。

  2. 階乗の代わりにガンマ関数を使用します。 _scipy.stats.loggamma_にあります。直接合計よりも対数階乗を計算する方がはるかに効率的な方法です。 loggamma(n) == log(factorial(n - 1))、および同様にgamma(n) == factorial(n - 1)

18
dsimcha

まだ言及されていないscipyの関数があります: scipy.special.comb 。 doctestのいくつかの迅速なタイミング結果(comb(100000, 1000, 1) == comb(100000, 99000, 1)の場合は〜0.004秒)に基づいて、効率的であるように見えます。

[この特定の質問はアルゴリズムに関するようですが、質問 Pythonには数学ncr関数があります はこれの複製としてマークされています...]

8
dshepherd

純粋なpythonソリューションが必要ない場合は、 gmpy2 が役立つ場合があります(gmpy2.combは非常に高速です)。

7
Alex Martelli

問題が順列または組み合わせの正確な数を知る必要がない場合は、階乗に スターリングの近似 を使用できます。

それはこのようなコードにつながるでしょう:

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
4
unutbu

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]
3
agorenst
from scipy import misc
misc.comb(n, k)

組み合わせを数えることができるはずです

3
Divyansh

NCrのより効率的なソリューション-スペースと精度の両方。

中間(res)は常にintであり、結果より大きくなることはありません。スペースの複雑さはO(1)(リストなし、zipなし、スタックなし)、時間の複雑さは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
2
ZXX
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))
1
Kumar

NにはKを選択し、パスカル三角形を使用できます。基本的に、N個すべてのK値を計算するには、サイズNの配列を維持する必要があります。追加のみが必要です。

0
Richie

xrange()の代わりにrange()を使用すると、中間リストが作成、入力、反復、および破棄されないため、処理速度が少し速くなります。また、reduce()operator.mul

Python= 3.7まで:

_def prod(
        items,
        start=1):
    for item in items:
        start *= item
    return start


def perm(n, k):
    if k > n or k < 0 or n < 0:
        raise ValueError(
            'Values must be non-negative and n >= k in perm(n, k)')
    else:
        return prod(range(n - k + 1, n + 1))


def comb(n, k):
    if k > n or k < 0 or n < 0:
        raise ValueError(
            'Values must be non-negative and n >= k in comb(n, k)')
    Elif k > n - k:
        # return perm(n, k) // math.factorial(n - k)
        return math.factorial(n) // math.factorial(k) // math.factorial(n - k)
    else:
        # return perm(n, n - k) // math.factorial(k)
        return math.factorial(n) // math.factorial(n - k) // math.factorial(k)
_

Python 3.8+の場合:

0
norok2

2つの整数を入力し、数学ライブラリをインポートして階乗を見つけ、nCr式を適用することができます

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