我需要计算一个数字的组合。
计算nCp的最快方法是什么,其中n>> p?
我需要一种快速的方法来为多项式方程生成二项式系数,我需要得到所有项的系数并将其存储在数组中。
(a + b)^ n = a ^ n + nC1 a ^(n-1)* b + nC2 a ^(n-2)* ............ + nC(n-1)a * b ^(n-1)+ b ^ n
计算nCp的最有效方法是什么?
答案 0 :(得分:15)
您需要使用动态编程来生成二项式系数
您可以创建一个数组,然后使用 O(N ^ 2)循环来填充它
C[n, k] = C[n-1, k-1] + C[n-1, k];
其中
C[1, 1] = C[n, n] = 1
在你的程序中,你可以得到C(n,k)值只是在[n,k]索引处查看你的2D数组
UPDATE 就像那样
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];
其中N,K - 你的n,k的最大值
答案 1 :(得分:13)
如果你需要为所有n计算它们,Ribtoks的答案可能是最好的。 对于单个n,你最好这样做:
C[0] = 1
for (int k = 0; k < n; ++ k)
C[k+1] = (C[k] * (n-k)) / (k+1)
除法是精确的,如果在乘法后完成。
请注意溢出C [k] *(n-k):使用足够大的整数。
答案 2 :(得分:8)
如果想要对n的大值进行完全展开,则FFT卷积可能是最快的方法。在具有相等系数的二项式展开(例如一系列公平的硬币投掷)和偶数顺序(例如投掷数量)的情况下,您可以利用对称性:
<强>理论强>
用表达式A + A * cos(Pi * n / N)表示两次硬币投掷的结果(例如,头部和尾部总数之差的一半)。 N是缓冲区中的样本数 - 偶数阶O的二项式扩展将具有O + 1个系数,并且需要N> = O / 2 + 1个样本的缓冲区 - n是生成的样本编号,A是比例因子通常为2(用于生成二项式系数)或0.5(用于生成二项式概率分布)。
请注意,在频率上,此表达式类似于这两个硬币投掷的二项分布 - 在与数字(头尾)/ 2对应的位置处有三个对称尖峰。由于对独立事件的整体概率分布进行建模需要对其分布进行卷积,我们希望在频域中对我们的表达进行卷积,这相当于时域中的乘法。
换句话说,通过将两次投掷的结果的余弦表达式提高到一个幂(例如,模拟500次投掷,将其提升到250的幂,因为它已经代表一对),我们可以安排二项分布大数出现在频域中。由于这一切都是真实的,我们可以用DCT-I代替DFT来提高效率。
<强>算法强>
<强>精度强>
在积累的浮点舍入误差为你提供系数的精确整数值之前,O的高度有限,但我猜这个数字非常高。双精度浮点可以完全准确地表示53位整数,我将忽略使用pow()时所涉及的舍入损失,因为生成表达式将在FP寄存器中发生,给我们额外的11尾数位用于吸收英特尔平台上的舍入误差。因此,假设我们使用通过FFT实现的1024点DCT-I,这意味着在变换期间丢失10位的精度以舍入误差,而不是其他多少,留下约43位的清晰表示。我不知道二项式扩展的顺序会产生那么大的系数,但我敢说它足以满足你的需求。
非对称扩展
如果你想要a和b的不等系数的非对称展开,你需要使用双面(复杂)DFT和复杂的pow()函数。生成表达式A * A * e ^( - Pi * i * n / N)+ A * B + B * B * e ^(+ Pi * i * n / N)[使用复杂的pow()函数来提高它是扩展顺序的一半]和DFT它。你在缓冲区中所拥有的是偏移零点处的中心点(但是如果A和B非常不同则不是最大值),然后是分布的上半部分。缓冲区的上半部分将包含分布的下半部分,对应于负数的head-minus-tails值。
请注意,源数据是Hermitian对称的(输入缓冲区的后半部分是第一个的复共轭),因此该算法不是最优的,可以使用复杂到复数的FFT执行一半所需的尺寸以获得最佳效率。
毋庸置疑,与上面对称分布的纯实数算法相比,所有复指数都会咀嚼更多的CPU时间并损害精度。
答案 3 :(得分:7)
这是我的版本:
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
答案 4 :(得分:7)
我最近编写了一段需要调用二进制系数大约1000万次的代码。所以我做了一个组合查找表/计算方法,仍然没有太浪费内存。您可能会发现它很有用(我的代码在公共域中)。代码在
http://www.etceterology.com/fast-binomial-coefficients
有人建议我在这里内联代码。一个大的鸣喇叭查找表似乎是浪费,所以这是最终的函数,以及生成表的Python脚本:
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 __name__ == "__main__":
sys.exit(main(sys.argv))
答案 5 :(得分:5)
如果你真的只需要n大于p的情况,一种方法是使用Stirling's formula作为阶乘。 (如果n>&gt;&gt; 1且p是1阶,Stirling接近n!和(n-p)!,保持p!原样等。)
答案 6 :(得分:4)
我自己的基准测试中最快的合理近似值是Apache Commons Maths库使用的近似值:http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/special/Gamma.html#logGamma(double)
我和我的同事试图看看我们是否可以击败它,同时使用精确的计算而非近似。所有方法都失败了(许多命令较慢),除了一个,慢了2-3倍。表现最佳的方法使用https://math.stackexchange.com/a/202559/123948,这是代码(在Scala中):
var i: Int = 0
var binCoeff: Double = 1
while (i < k) {
binCoeff *= (n - i) / (k - i).toDouble
i += 1
}
binCoeff
使用尾递归实现Pascal三角形的各种尝试的非常糟糕的方法。
答案 7 :(得分:2)
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 )
如果我们修剪分子和分母的相同项,我们只需要最小的乘法。我们可以在C中写一个函数来执行2p乘法和1个除法来得到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;
}
答案 8 :(得分:1)
我一直在寻找相同的东西而找不到它,所以我自己写了一篇对于任何二项式系数来说都是最优的,而这种二项式系数对于它来说是最好的。
// 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;
}
答案 9 :(得分:0)
如果我理解问题中的符号,你不仅仅想要nCp,你实际上想要所有的nC1,nC2,...... nC(n-1)。如果这是正确的,我们可以利用以下关系来实现这一点:
这是一个实现这种方法的python片段:
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
如果你需要所有系数高达某些k>天花板(n / 2),您可以使用对称性来减少需要执行的操作次数,方法是停止上限系数(n / 2),然后根据需要进行回填。
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
答案 10 :(得分:0)
时间复杂度:O(分母) 空间复杂度:O(1)
public class binomialCoeff {
static double binomialcoeff(int numerator, int denominator)
{
double res = 1;
//invalid numbers
if (denominator>numerator || denominator<0 || numerator<0) {
res = -1;
return res;}
//default values
if(denominator==numerator || denominator==0 || numerator==0)
return res;
// Since C(n, k) = C(n, n-k)
if ( denominator > (numerator - denominator) )
denominator = numerator - denominator;
// Calculate value of [n * (n-1) *---* (n-k+1)] / [k * (k-1) *----* 1]
while (denominator>=1)
{
res *= numerator;
res = res / denominator;
denominator--;
numerator--;
}
return res;
}
/* Driver program to test above function*/
public static void main(String[] args)
{
int numerator = 120;
int denominator = 20;
System.out.println("Value of C("+ numerator + ", " + denominator+ ") "
+ "is" + " "+ binomialcoeff(numerator, denominator));
}
}