C ++中的组合数(N选择R)

时间:2012-02-17 15:26:42

标签: c++ combinatorics binomial-coefficients

这里我尝试用C ++编写程序来查找NCR。但是我在结果中遇到了问题。这是不正确的。你能帮我找一下程序中的错误吗?

#include <iostream>
using namespace std;
int fact(int n){
    if(n==0) return 1;
    if (n>0) return n*fact(n-1);
};

int NCR(int n,int r){
    if(n==r) return 1;
    if (r==0&&n!=0) return 1;
    else return (n*fact(n-1))/fact(n-1)*fact(n-r);
};

int main(){
    int n;  //cout<<"Enter A Digit for n";
    cin>>n;
    int r;
         //cout<<"Enter A Digit for r";
    cin>>r;
    int result=NCR(n,r);
    cout<<result;
    return 0;
}

7 个答案:

答案 0 :(得分:21)

你的公式是完全错误的,它应该是fact(n)/fact(r)/fact(n-r),但这反过来又是一种非常低效的计算方法。

请参阅Fast computation of multi-category number of combinations,尤其是我对该问题的评论。 (哦,请重新打开这个问题,这样我就能正确回答)

单分割案例实际上非常容易处理:

unsigned nChoosek( unsigned n, unsigned k )
{
    if (k > n) return 0;
    if (k * 2 > n) k = n-k;
    if (k == 0) return 1;

    int result = n;
    for( int i = 2; i <= k; ++i ) {
        result *= (n-i+1);
        result /= i;
    }
    return result;
}

演示:http://ideone.com/aDJXNO

如果结果不合适,您可以计算对数之和,并将不精确的组合数作为double。或者使用任意精度的整数库。


我正在将解决方案放在另一个密切相关的问题上,因为ideone.com最近一直在丢失代码片段,另一个问题仍然是新答案。

#include <utility>
#include <vector>

std::vector< std::pair<int, int> > factor_table;
void fill_sieve( int n )
{
    factor_table.resize(n+1);
    for( int i = 1; i <= n; ++i )
        factor_table[i] = std::pair<int, int>(i, 1);
    for( int j = 2, j2 = 4; j2 <= n; (j2 += j), (j2 += ++j) ) {
        if (factor_table[j].second == 1) {
            int i = j;
            int ij = j2;
            while (ij <= n) {
                factor_table[ij] = std::pair<int, int>(j, i);
                ++i;
                ij += j;
            }
        }
    }
}

std::vector<unsigned> powers;

template<int dir>
void factor( int num )
{
    while (num != 1) {
        powers[factor_table[num].first] += dir;
        num = factor_table[num].second;
    }
}

template<unsigned N>
void calc_combinations(unsigned (&bin_sizes)[N])
{
    using std::swap;

    powers.resize(0);
    if (N < 2) return;

    unsigned& largest = bin_sizes[0];
    size_t sum = largest;
    for( int bin = 1; bin < N; ++bin ) {
        unsigned& this_bin = bin_sizes[bin];
        sum += this_bin;
        if (this_bin > largest) swap(this_bin, largest);
    }
    fill_sieve(sum);

    powers.resize(sum+1);
    for( unsigned i = largest + 1; i <= sum; ++i ) factor<+1>(i);
    for( unsigned bin = 1; bin < N; ++bin )
        for( unsigned j = 2; j <= bin_sizes[bin]; ++j ) factor<-1>(j);
}

#include <iostream>
#include <cmath>
int main(void)
{
    unsigned bin_sizes[] = { 8, 1, 18, 19, 10, 10, 7, 18, 7, 2, 16, 8, 5, 8, 2, 3, 19, 19, 12, 1, 5, 7, 16, 0, 1, 3, 13, 15, 13, 9, 11, 6, 15, 4, 14, 4, 7, 13, 16, 2, 19, 16, 10, 9, 9, 6, 10, 10, 16, 16 };
    calc_combinations(bin_sizes);
    char* sep = "";
    for( unsigned i = 0; i < powers.size(); ++i ) {
        if (powers[i]) {
            std::cout << sep << i;
            sep = " * ";
            if (powers[i] > 1)
                std::cout << "**" << powers[i];
        }
    }
    std::cout << "\n\n";
}

答案 1 :(得分:4)

N选择R的定义是计算两个产品并将一个产品与另一个产品分开,

(N * N-1 * N-2 * ... * N-R + 1)/(1 * 2 * 3 * ... * R)

但是,乘法可能会变得非常快,并且溢出现有数据类型。实现技巧是将乘法和除法重新排序为,

(N)/ 1 *(N-1)/ 2 *(N-2)/ 3 * ... *(N-R + 1)/ R

保证在每一步中结果都是可分的(对于n个连续数字,其中一个必须可被n整除,因此这些数字的乘积也是如此)。

例如,对于N选择3,N,N-1,N-2中的至少一个将是3的倍数,并且对于N选择4,N,N-1,N中的至少一个2,N-3将是4的倍数。

下面给出的C ++代码。

int NCR(int n, int r)
{
    if (r == 0) return 1;

    /*
     Extra computation saving for large R,
     using property:
     N choose R = N choose (N-R)
    */
    if (r > n / 2) return NCR(n, n - r); 

    long res = 1; 

    for (int k = 1; k <= r; ++k)
    {
        res *= n - k + 1;
        res /= k;
    }

    return res;
}

答案 2 :(得分:3)

实现n-choose-k的一个好方法是不是基于阶乘,而是基于&#34;不断上升的产品&#34;与阶乘密切相关的功能。

rising_product(m,n)将m *(m + 1)*(m + 2)* ... * n与用于处理各种角点情况的规则相乘,例如n> = m或n&lt; ; = 1:

请参阅here了解实现nCk以及nPk作为用C编写的解释编程语言中的内部函数:

static val rising_product(val m, val n)
{
  val acc;

  if (lt(n, one))
    return one;

  if (ge(m, n))
    return one;

  if (lt(m, one))
    m = one;

  acc = m;

  m = plus(m, one);

  while (le(m, n)) {
    acc = mul(acc, m);
    m = plus(m, one);
  }

  return acc;
}

val n_choose_k(val n, val k)
{
  val top = rising_product(plus(minus(n, k), one), n);
  val bottom = rising_product(one, k);
  return trunc(top, bottom);
}

val n_perm_k(val n, val k)
{
  return rising_product(plus(minus(n, k), one), n);
}

此代码不使用+<等运算符,因为它是类型泛型(类型val表示任何类型的值,例如各种数字包括&#34; bignum&#34;整数),因为它是用C语言编写的(没有重载),因为它是类似Lisp的语言的基础,没有中缀语法。

尽管如此,这个n-choose-k实现具有易于理解的简单结构。

图例:le:小于或等于; ge:大于或等于; trunc:截断分割; plus:此外,mul:乘法,one:第一个val类型常量。

答案 3 :(得分:1)

使用double代替int

更新:

你的公式也错了。您应该使用fact(n)/fact(r)/fact(n-r)

答案 4 :(得分:1)

else return (n*fact(n-1))/fact(n-1)*fact(n-r);

应该是

else return (n*fact(n-1))/(fact(r)*fact(n-r));

甚至

else return fact(n)/(fact(r)*fact(n-r));

答案 5 :(得分:0)

这是为了参考在竞争性编程中解决nCr时没有超出时间限制,我发布这个,因为它对你有帮助,因为你已经得到了你的问题的答案, 获得二项式系数的素数因子化可能是计算它的最有效方法,特别是如果乘法很昂贵。计算阶乘的相关问题肯定是正确的(例如,见Click here)。

这是一个基于Eratosthenes筛选的简单算法,用于计算素数分解。这个想法基本上是通过使用筛子找到它们的质数,但是也可以计算它们的多少倍数落在[1,k]和[n-k + 1,n]的范围内。 Sieve本质上是一个O(n \ log \ log n)算法,但没有完成乘法。找到素数因子分解后所需的实际乘法次数最差为O \ left(\ frac {n \ log \ log n} {\ log n} \ right),并且可能有更快的方法。

prime_factors = []

n = 20
k = 10

composite = [True] * 2 + [False] * n

for p in xrange(n + 1):
if composite[p]:
    continue

q = p
m = 1
total_prime_power = 0
prime_power = [0] * (n + 1)

while True:

    prime_power[q] = prime_power[m] + 1
    r = q

    if q <= k:
        total_prime_power -= prime_power[q]

    if q > n - k:
        total_prime_power += prime_power[q]

    m += 1
    q += p

    if q > n:
        break

    composite[q] = True

prime_factors.append([p, total_prime_power])

 print prime_factors

答案 6 :(得分:0)

递归函数在这里使用不正确。 fact()函数应更改为此:

int fact(int n){
if(n==0||n==1) //factorial of both 0 and 1 is 1. Base case.
{
    return 1;
}else

    return (n*fact(n-1));//recursive call.

};

应在else部分进行递归调用。

NCR()函数应更改为此:

int NCR(int n,int r){
    if(n==r) {
        return 1;
    } else if (r==0&&n!=0) {
        return 1;
    } else if(r==1)
    {
        return n;
    }
    else
    {
        return fact(n)/(fact(r)*fact(n-r));
    }
};