的公式nCr(n,k)
是:
| n | n!
| | = ---------
| k | k!.(n-k)!
问题是阶乘很快就会变得非常大,即使对于小的输入也会溢出标准变量。为了避免这种情况,我们只是消除了冗余操作......我可以重写为:
| n | n! 1*2*3*...*n
| | = --------- = -----------------------------
| k | k!.(n-k)! 1*2*3*...*k * 1*2*3*...*(n-k)
现在我们可以看到除法两边的第一个n-r
或k
(取决于哪个更大)乘法相同,因此我们可以跳过它们(以防万一k>=n-r
):
| n | n! (k+1)*(k+2)*(k+3)*...*n
| | = --------- = -----------------------------
| k | k!.(n-k)! 1*2*3*...*(n-k)
此外,如果我们在循环中执行此操作并在每次乘法后除法,则子结果将保持较小:
| n | n! (k+1) (k+2) (k+3) (n)
| | = --------- = ----- * ----- * ----- * ... * -----
| k | k!.(n-k)! 1 2 3 (n-k)
是的,该部门的两侧都有相同数量的therms。如果我理解正确,您的代码应该这样做nCr(m+n-2,n-1)
,匹配公式的替换将是:
n` = m+n-2
k` = n-1
重写为:
| m+n-2 | (n-1+1) (n-1+2) (n-1+3) (m+n-2)
| | = ------- * ------- * ------- * ... * -----------
| n-1 | 1 2 3 (m+n-2-n+1)
| m+n-2 | (n) (n+1) (n+2) (m+n-2)
| | = --- * ----- * ----- * ... * -------
| n-1 | 1 2 3 (m-1)
所以你的循环正在做一个与上面的等式匹配的PI
地方i/(i-n+1)
......i={ n,n+1,...,m+n-1 }
请注意,这并不准确nCr
,因为它需要在浮点数上计算,因此每次迭代都会出现舍入错误!!!所以输出可能会稍微偏离一点!!!然而,这可以以类似的方式在整数上计算(没有任何精度损失),但不是在每次迭代中除法,而是将两个除数与公约数除以保持它们“小”。理想情况下是前几个素数。这里有一个小的 C++ 示例(浮点和 int 版本),我只是匆匆忙忙:
//---------------------------------------------------------------------------
//
// | n | n! combinations = fact(n)/(fact(k)*fact(n-k))
// | | = --------- how many combinations of k items from n items are possible
// | k | k!.(n-k)! when order does not matter
//
DWORD nCr(DWORD n,DWORD k)
{
DWORD a,b,ia,ib,j,m,p;
const DWORD prime[]={2,3,5,7,11,13,17,19,23,29,31,0};
if (k> n) return 0;
if (k==n) return 1;
m=n-k;
for (a=1,b=1,ia=k+1,ib=2;(ia<=n)||(ib<=m);)
{
if ((b<=a)&&(ib<=m)){ b*=ib; ib++; } // multiply the smaller number if possible
else if (ia<=n) { a*=ia; ia++; }
for (;((a|b)&1)==0;a>>=1,b>>=1); // divide a,b by 2 if possible
for (j=1;;j++) // divide a,b by next few prmes (skip 2) if possible
{
p=prime[j];
if (!p) break;
if (a<p) break;
if (b<p) break;
for (;(a%p)+(b%p)==0;a/=p,b/=p);
}
}
return a/b;
}
//---------------------------------------------------------------------------
float nCr_approx(DWORD n,DWORD k)
{
if (k> n) return 0;
if (k==n) return 1;
float c;
DWORD i,m=n-k;
for (c=1.0,i=1;i<=m;i++)
{
c*=(k+i);
c/=(i);
}
return c;
}
//---------------------------------------------------------------------------
哪里DWORD
是 32 位无符号整数(但可以使用任何整数变量类型)......这可以正常工作(在 32 位上)直到nCr(32,15)
这里两者之间的比较:
n k nCr(n,k) nCr_approx(n,k)
32 0 1 1.000
32 1 32 32.000
32 2 496 496.000
32 3 4960 4960.000
32 4 35960 35960.000
32 5 201376 201376.000
32 6 906192 906191.938 *** float is off
32 7 3365856 3365856.000
32 8 10518300 10518300.000
32 9 28048800 28048802.000 *** float is off
32 10 64512240 64512240.000
32 11 129024480 129024488.000 *** float is off
32 12 225792840 225792864.000 *** float is off
32 13 347373600 347373632.000 *** float is off
32 14 471435600 471435584.000 *** float is off
32 15 565722720 565722688.000 *** float is off
32 16 64209478 601080384.000 *** int overflow
32 17 565722720 565722752.000 *** float is off
32 18 471435600 471435584.000 *** float is off
32 19 347373600 347373600.000
32 20 225792840 225792832.000 *** float is off
32 21 129024480 129024488.000 *** float is off
32 22 64512240 64512236.000 *** float is off
32 23 28048800 28048800.000
32 24 10518300 10518299.000 *** float is off
32 25 3365856 3365856.000
32 26 906192 906192.000
32 27 201376 201376.000
32 28 35960 35960.000
32 29 4960 4960.000
32 30 496 496.000
32 31 32 32.000
32 32 1 1.000
是的,您可以double
改用,但请始终牢记结果可能会略有偏差!