由于因子溢出导致的组合问题

时间:2014-07-25 14:59:01

标签: java c# math

我需要一个能够计算纸牌游戏的mathematical combination(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值max最大值为4),它会让我返回错误值。例如:

   long comb = Combinatory(52, 2) ; // return 1 which should be actually 1326

我知道这是因为当我制作Factorial(52)时,我已经过了很长时间,但我需要的范围结果并不像它看起来那么大。

有没有办法解决这个问题?

4 个答案:

答案 0 :(得分:10)

使用组合函数的递归属性,而不是使用默认的组合公式n! / (k! x (n - k)!)

    (n, k) = (n - 1, k) + (n - 1, k - 1)

了解:(n, 0) = 1(n, n) = 1

<强> - &GT;它会让你避免使用阶乘和溢出你的长期。

以下是您可以执行的实施示例:

 static long Combinatory(long n, long k)
    {
        if (k == 0 || n == k )
            return 1;

        return Combinatory(n - 1, k) + Combinatory(n - 1, k - 1); 
    }

编辑:使用更快的迭代算法

    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;
    } 

答案 1 :(得分:2)

在C#中你可以使用BigInteger(我认为有一个Java等价物。)

e.g:

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);
}

您需要添加对System.Numerics的引用才能使用BigInteger。

答案 2 :(得分:2)

如果这不是家庭作业,那么在Apache的公共数学包中有一个有效的实现

http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/util/ArithmeticUtils.html#binomialCoefficientDouble%28int,%20int%29

如果是家庭作业,请在实施中开始避免使用因子。

使用(n,k)=(n,n-k)的属性使用k的最高值重写您的选择。

然后注意你可以减少n!/ k!(n-k)!到n * n-1 * n-2 .... * k /(nk)*(nk-1)... * 1表示您将[k,n]中的每个数相乘,然后除以数字[1,nk]包括在内。

// 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;
}

请仔细检查数学,但这是一个基本的想法,你可以采取一个合理有效的实施,适用于数字到扑克牌。

答案 3 :(得分:0)

递归公式也称为Pascal's triangle,IMO是计算组合的最简单方法。如果您只需要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