C++中的组合数(N选择R)
Number of combinations (N choose R) in C++
这里我尝试用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;
}
你的公式完全错误,应该是fact(n)/fact(r)/fact(n-r)
,但这反过来又是一种非常低效的计算方法。
请参阅多类别组合数的快速计算,尤其是我对该问题的评论。(哦,请重新打开这个问题,这样我就可以正确回答了)
单个拆分案例实际上非常容易处理:
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
如果结果不合适,你可以计算对数的和,并得到不精确的组合数作为二重。或者使用任意精度的整数库。
我在这里提出了另一个密切相关的问题的解决方案,因为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 << "nn";
}
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;
}
rising_product(m,n)乘以m*(m+1)*(m+2)*…*n、 具有处理各种拐角情况的规则,如n>=m或n<=1:
请参阅此处,了解用C:编写的解释编程语言中作为内部函数的nCk和nPk的实现
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
表示任何类型的值,例如包括"bignum"整数在内的各种数字),因为它用C编写(没有重载),而且它是没有中缀语法的类Lisp语言的基础。
尽管如此,这种n-choose-k实现具有易于遵循的简单结构。
图例:le
:小于或等于;ge
:大于或等于;trunc
:截断除法;plus
:加法,mul
:乘法,one
:数字1的val
型常数。
行
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));
使用double
而不是int
。
更新:
你的公式也是错误的。您应该使用fact(n)/fact(r)/fact(n-r)
这是为了在竞争性编程中解决nCr时不要超过时间限制,我发布这篇文章是因为它对你有帮助,因为你已经得到了你的问题的答案,对二项式系数进行素数分解可能是计算二项式的最有效方法,尤其是在乘法运算成本高昂的情况下。这当然适用于计算阶乘的相关问题(例如,请参见单击此处)。
这里有一个基于埃拉托斯梯尼筛的简单算法,用于计算素数分解。这个想法基本上是当你用筛子找到素数时,先遍历它们,然后计算它们的倍数中有多少落在[1,k]和[n-k+1,n]的范围内。筛本质上是一个O(n\log\logn)算法,但没有进行乘法运算。一旦找到素数因子分解,实际需要的乘法次数最差为O\left(\frac{n\log\logn}{\logn}\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
此处未正确使用递归函数。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));
}
};
// CPP program To calculate The Value Of nCr
#include <bits/stdc++.h>
using namespace std;
int fact(int n);
int nCr(int n, int r)
{
return fact(n) / (fact(r) * fact(n - r));
}
// Returns factorial of n
int fact(int n)
{
int res = 1;
for (int i = 2; i <= n; i++)
res = res * i;
return res;
}
// Driver code
int main()
{
int n = 5, r = 3;
cout << nCr(n, r);
return 0;
}
- 组合框第一行不可选择
- 组合框选择加载以前的选择
- 如果我们从每个 Y 向量中选择一个值,则 X 数字的每个组合都可能
- 选择和 UDP 协议组合时无法接收数据
- 选择元素的所有组合
- 如何使用递归从集合中选择所有可能的元素组合
- 在win API中带有图像和整行选择的组合框
- Qt C++根据组合框中的选择创建一个对象
- 当我在C++中从组合框中选择行时,如何调用函数
- 函数从c++中的字符串中选择字符组合
- Qt 组合框:选择项目时的事件
- 在组合框选择上更改日期时间选取器样式失败
- 我想为每个组合框选择分配整数值,并将它们集中打印到文本文件中
- 是否可以使用C++对象组合,其中对象的选择是在运行时确定的
- 从一个组合框中选择项目,然后从其他组合框中删除该项目
- 根据组合框中的选定项目选择算法
- 基于QTCreator中的组合框选择修改样式表
- QInputDialog操作取决于组合框中的项目选择
- 当从表视图中选择一行时,为组合框指定一个int值
- 使用单个sendmessage选择组合框项