计算组合数的算法

概率论是统计分析的基础,而统计分析在很多领域比如人工智能,生物信息学中作为理论基础,具有广泛的应用场景。组合数在概率论中常用,本文就对组合数的数值计算给出一个C++求解的算法。通过实现该算法,对数值计算上的某些方法加深的认识。
首先,来看一下组合数的公式:
这里写图片描述
通过该公式,首先会想到使用暴力求解法来计算。但阶乘的计算,很容易就会造成数值的溢出,因此暴力求解法在数值稍大的情况下就无法适用。
紧接着,想到常用的处理溢出的方法,利用转换成对数进行运算,使用该种方法可将连乘转换成连加,溢出问题是解决了,但精度上却又不能满足要求。
于是,需要探索其他的方法来解决这一问题。考虑到分子分母均为阶乘,如果对分子分母分别分解质因数,然后对共同的质因数进行约分,最终剩下的质因数的乘积就是我们最终要求的结果。使用这一方法的好处,还在于我们可以通过记录质因数及质因数存在的次数来记录最终的结果,而不必直接记录最终的结果,这样就可以同时解决溢出问题和精度问题。

在本文最后,我附上了具体的C++实现的代码。在看代码之前,我们先将其中的核心部分进行一下解读与分析。
1.初始化素数动态数组(C++的动态数组可以用STL中的向量来实现)。在判断某个数是否素数时,我们只需要轮循到该数的平方根即可,这将大大减少内层循环的算法复杂度。
这里写图片描述
2.n的阶乘里面含有素数的次数。因为n!表示n(n-1)(n-2)…1,因此如果n>=x(素数),则必然至少存在n/x次素数x,并且这n/x个数除以素数x之后得到的新的排列中,又会又(n/x)/x次素数x。换句话说,素数x的所有次数,均会包含在第一次循环能够整除x的这些数中,而这些数中有的可能包括多次的素数x。因此,通过getPrimePow方法,则得到了n!里面含有素数x的所有次数,记为pow返回。
这里写图片描述
3.计算组合数。根据组合数的公式,可以看到,通过计算n!中含有某个素数的次数,m!中含有这个素数的次数和(n-m)!中含有素数的次数,就可以得到最终结果中应该含有几个该素数。
这里写图片描述
4.将结果保存在C++ STL中的Map结构中。Map的key使用素数做下标即可,值即为该素数在最终结果中的次数。为防止溢出,最终结果用该种方式保存即可。

具体的算法如下:

#ifndef _COMBINE_CALCULATOR_H_
#define _COMBINE_CALCULATOR_H_
#include <vector>
#include <map>
#include <iostream>
using namespace std;

class CombineCalculator
{
public:
    CombineCalculator();
    ~CombineCalculator();
    //计算组合数,C(n,m)
    bool calculateCombine(int n, int m);
    //打印结果
    void printResult();
private:
    //计算n!中含有素数x的次数
    int getPrimePow(int n, int x);
    //生成素数
    void generatePrime();
    //存放素数动态数组
    vector<int> primeVec;
    //结果Map,key为素数,value为该素数的次数
    map<int, int> resultMap;
    const int MAXN = 1000;
    const int MAXPRIME = 1000;
};

#endif
#include "CombineCalculator.h"

CombineCalculator::CombineCalculator()
{
    generatePrime();
}

CombineCalculator::~CombineCalculator()
{

}

void CombineCalculator::generatePrime()
{
    for (int i = 2; i < MAXPRIME; i++)
    {
        bool isPrime = true;
        int s = sqrt(i);
        for (int j = 2; j <= s; j++)
        {
            if (i%j == 0)
            {
                isPrime = false;
                break;
            }
        }
        if (isPrime)
        {
            primeVec.push_back(i);
        }
    }
    return;
}


int CombineCalculator::getPrimePow(int n, int x)
{
    int pow = 0;
    while (n >= x)
    {
        int temp = n / x;
        pow += temp;
        n = temp;
    }
    return pow;
}


bool CombineCalculator::calculateCombine(int n, int m)
{
    bool result = true;
    if (n > MAXN)
    {
        result = false;

    }
    else
    {
        for (int i = 0; i < primeVec.size(); i++)
        {
            int pow = getPrimePow(n, primeVec[i]) - getPrimePow(m, primeVec[i]) - getPrimePow(n - m, primeVec[i]);
            if (pow != 0)
                resultMap.insert(std::pair<int,int>(primeVec[i], pow));
        }
    }
    return result;
}

void CombineCalculator::printResult()
{
    map<int, int>::const_iterator iter;
    for (iter = resultMap.begin(); iter != resultMap.end(); iter++)
    {
        cout << "Key:" << iter->first << "Value:" << iter->second << endl;
    }
}
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值