由于Factorial溢出导致的组合问题
本文关键字:组合 问题 Factorial 溢出 由于 | 更新日期: 2023-09-27 18:14:57
我需要一个可以计算纸牌游戏中(n, k)的数学组合的函数。
我目前的尝试是使用一个基于通常的Factorial方法的函数:
static long Factorial(long n)
{
return n < 2 ? 1 : n * Factorial(n - 1);
}
static long Combinatory(long n , long k )
{
return Factorial(n) / (Factorial(k) * Factorial(n - k));
}
它工作得很好,但问题是,当我使用一些数字范围(n值最大值是52,k值最大值是4),它让我返回一个错误的值。例如:
long comb = Combinatory(52, 2) ; // return 1 which should be actually 1326
我知道这是因为我在Factorial(52)时溢出了long,但是我需要的范围结果并没有看起来那么大。
有办法解决这个问题吗?
不使用默认的组合公式n! / (k! x (n - k)!)
,而是使用组合函数的递归属性。
(n, k) = (n - 1, k) + (n - 1, k - 1)
知道:(n, 0) = 1
和(n, n) = 1
。
->它将使您避免使用factorial和溢出您的long。
下面是你可以做的实现示例:
static long Combinatory(long n, long k)
{
if (k == 0 || n == k )
return 1;
return Combinatory(n - 1, k) + Combinatory(n - 1, k - 1);
}
EDIT:使用更快的迭代算法
static long Combinatory(long n, long k)
{
if (n - k < k)
k = n - k;
long res = 1;
for (int i = 1; i <= k; ++i)
{
res = (res * (n - i + 1)) / i;
}
return res;
}
在c#中你可以使用BigInteger(我想Java中也有类似的)。
例如:
static long Combinatory(long n, long k)
{
return (long)(Factorial(new BigInteger(n)) / (Factorial(new BigInteger(k)) * Factorial(new BigInteger(n - k))));
}
static BigInteger Factorial(BigInteger n)
{
return n < 2 ? 1 : n * Factorial(n - 1);
}
使用BigInteger需要添加System.Numerics
的引用
如果这不是用于家庭作业,那么在Apache的commons-math包中有一个有效的实现
http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/util/ArithmeticUtils.html binomialCoefficientDouble % 28 int, int % 20 % 29日
如果它是一个家庭作业,在你的实现中开始避免阶乘。
使用属性(n, k) = (n, n-k)重写您的选择,使用k的最大值。
然后注意你可以约简n!/k!(n-k)!到n * n-1 * n-2 ....* k/(n-k) * (n-k-1)…* 1意味着你要乘以[k, n]中的所有数字,然后除以[1,n-k]中的所有数字。
// From memory, please verify correctness independently before trusting its use.
//
public long choose(n, k) {
long kPrime = Math.max(k, n-k);
long returnValue = 1;
for(i = kPrime; i <= n; i++) {
returnValue *= i;
}
for(i = 2; i <= n - kPrime; i++) {
returnValue /= i;
}
return returnValue;
}
请仔细检查数学,但这是一个基本的想法,你可以去得到一个合理有效的实现,将适用于数字到扑克牌。
递归公式也被称为帕斯卡三角形,在我看来,这是计算组合数最简单的方法。如果你只需要C(52,k)(对于0<=k<=52),我认为最好在程序开始时用它们填一个表。下面的C代码使用这个方法填充一个表:
static int64_t* pascals_triangle( int N)
{
int n,k;
int64_t* C = calloc( N+1, sizeof *C);
for( n=0; n<=N; ++n)
{ C[n] = 1;
for( k=n-1; k>0; --k)
{ C[k] += C[k-1];
}
}
return C;
}
调用N=52后,例如返回,C[k]将保存C(52,k),k =0..52