C ++二项式系数太慢

时间:2019-03-29 16:34:52

标签: c++ recursion math

我尝试通过对Pascal三角形进行递归来计算二项式系数。它适用于少量数字,但是20以上的速度真的很慢或根本不起作用。

我试图查找一些优化技术,例如“ chaching”,但是它们似乎并没有很好地集成在C ++中。

如果可以的话,这是代码。

int binom(const int n, const int k)
{
    double sum;

    if(n == 0 || k == 0){
            sum = 1;
    }
    else{
    sum = binom(n-1,k-1)+binom(n-1,k);
    }

    if((n== 1 && k== 0) || (n== 1 && k== 1))
       {
           sum = 1;
       }
    if(k > n)
    {
        sum = 0;
    }

    return sum;

}

int main()
{
int n;
int k;
int sum;

cout << "Enter a n: ";
cin >> n;
cout << "Enter a k: ";
cin >> k;

Summe = binom(n,k);

cout << endl << endl << "Number of possible combinations: " << sum << 
endl;

}

我的猜测是该程序浪费了很多时间来计算已经计算出的结果。它必须以某种方式记住过去的结果。

5 个答案:

答案 0 :(得分:2)

  

我的猜测是该程序浪费了很多时间来计算已经计算出的结果。

那是真的。

关于这个主题,建议您看看Dynamic Programming Topic

有一类问题,它要求指数级的运行时复杂度,但是可以通过动态编程技术解决。 这样可以将运行时复杂度降低为多项式复杂度(大多数情况下,以增加空间复杂度为代价)。


动态编程的常用方法是:

  • 自顶向下(利用记忆和递归)。
  • 自下而上(迭代)。

以下是我的自下而上解决方案(快速紧凑):

int BinomialCoefficient(const int n, const int k) {
  std::vector<int> aSolutions(k);
  aSolutions[0] = n - k + 1;

  for (int i = 1; i < k; ++i) {
    aSolutions[i] = aSolutions[i - 1] * (n - k + 1 + i) / (i + 1);
  }

  return aSolutions[k - 1];
}

该算法具有运行时复杂度O(k)和空间复杂度O(k)。 确实,这是线性的。

此外,该解决方案比递归方法更简单,更快。这对CPU缓存非常友好。

请注意,n也没有依赖性。

我通过简单的数学运算并获得以下公式获得了此结果:

(n, k) = (n - 1, k - 1) * n / k

Some math references on the Binomial Coeffient


注意

该算法实际上并不需要O(k)的空间复杂度。 实际上,第 th 步的解决方案仅取决于(i-1)-th 。 因此,无需存储所有中间解决方案,只需存储上一步中的解决方案即可。这样就可以使算法O(1)的空间复杂度。

但是,我宁愿将所有中间解决方案保留在解决方案代码中,以更好地展示动态编程方法的原理。

Here my repository with the optimized algorithm

答案 1 :(得分:1)

您正在多次计算一些二项式值。快速的解决方案是记忆。

未经测试:

int binom(int n, int k);

int binom_mem(int n, int k)
{
    static std::map<std::pair<int, int>, std::optional<int>> lookup_table;
    auto const input = std::pair{n,k};
    if (lookup_table[input].has_value() == false) {
        lookup_table[input] = binom(n, k);
    }
    return lookup_table[input];
}

int binom(int n, int k)
{
    double sum;

    if (n == 0 || k == 0){
        sum = 1;
    } else {
        sum = binom_mem(n-1,k-1) + binom_mem(n-1,k);
    }

    if ((n== 1 && k== 0) || (n== 1 && k== 1))
    {
        sum = 1;
    }
    if(k > n)
    {
        sum = 0;
    }

    return sum;
}

一个更好的解决方案是转向递归tailrec(使用两次递归都不容易),或者更好,根本不使用递归;)

答案 2 :(得分:1)

我会将每个计算的结果缓存在地图中。您无法使用复杂的键制作地图,但可以将键转换为字符串。

string key = string("") + n.to_s() + "," + k.to_s();

然后有一张全球地图:

map<string, double> cachedValues;

然后您可以使用该键进行查找,如果找到,请立即返回。否则,在返回之前,请先存储在地图上。

我开始计划调用4,5会发生什么。杂乱无章,进行了大量的计算。每深入一层,就会进行2 ^ n次查找。

我不知道您的基本算法是否正确,但是如果是,那么我会将这段代码移到方法的顶部:

if(k > n)
{
    return 0;
}

看起来,如果k> n,即使对于6,100之类的值,您总是返回0。但是,我不知道这是否正确。

答案 3 :(得分:1)

基于此证明(由我编写),我发现即使对于非整数也可以使用这种非常简单(可能有点慢)的方法来编写二项式系数:

double binomial_coefficient(float k, int a) {
    double b=1;
    for(int p=1; p<=a; p++) {
        b=b*(k+1-p)/p;
    }
    return b;
}

答案 4 :(得分:0)

如果你能容忍浪费一些编译时内存,你可以在编译时预先计算一个 Pascal-Triangle。通过简单的查找机制,这将为您提供最大速度。

缺点是最多只能计算到第 69 行。之后,即使是 unsigned long long 也会溢出。

因此,我们只需使用 constexpr 函数并在二维编译时 constexpr std::array 中计算帕斯卡三角形的值。

nCr 函数只是使用该数组的索引(进入帕斯卡三角形)。

请看下面的示例代码:

#include <iostream>
#include <utility>
#include <array>
#include <iomanip>
#include <cmath>

// Biggest number for which nCR will work with a 64 bit variable: 69
constexpr size_t MaxN = 69u;
// If we store Pascal Triangle in a 2 dimensional array, the size will be that
constexpr size_t ArraySize = MaxN;

// This function will generate Pascals triangle stored in a 2 dimension std::array
constexpr auto calculatePascalTriangle() {

    // Result of function. Here we will store Pascals triangle as a 1 dimensional array
    std::array<std::array<unsigned long long, ArraySize>, ArraySize> pascalTriangle{};

    // Go through all rows and columns of Pascals triangle
    for (size_t row{}; row < MaxN; ++row) for (size_t col{}; col <= row; ++col) {

        // Border valus are always one
        unsigned long long result{ 1 };
        if (col != 0 && col != row) {

            // And calculate the new value for the current row
            result = pascalTriangle[row - 1][col - 1] + pascalTriangle[row - 1][col];
        }
        // Store new value
        pascalTriangle[row][col] = result;
    }
    // And return array as function result
    return pascalTriangle;
}
// This is a constexpr std::array<std::array<unsigned long long,ArraySize>, ArraySize> with the name PPP, conatining all nCr results 
constexpr auto PPP = calculatePascalTriangle();

// To calculate nCr, we used look up the value from the array
constexpr unsigned long long nCr(size_t n, size_t r) {
    return PPP[n][r];
}

// Some debug test driver code. Print Pascal triangle
int main() {
    constexpr size_t RowsToPrint = 16u;
    const size_t digits = static_cast<size_t>(std::ceil(std::log10(nCr(RowsToPrint, RowsToPrint / 2))));
    for (size_t row{}; row < RowsToPrint; ++row) {

        std::cout << std::string((RowsToPrint - row) * ((digits + 1) / 2), ' ');

        for (size_t col{}; col <= row; ++col)
            std::cout << std::setw(digits) << nCr(row, col) << ' ';
        std::cout << '\n';
    }
    return 0;
}

我们还可以将帕斯卡三角形存储在一维 constexpr std::array 中。但是我们需要额外计算三角形数来找到一行的起始索引。但这也可以在编译时完全完成。

那么解决方案将如下所示:

#include <iostream>
#include <utility>
#include <array>
#include <iomanip>
#include <cmath>

// Biggest number for which nCR will work with a 64 bit variable
constexpr size_t MaxN = 69u; //14226520737620288370
// If we store Pascal Triangle in an 1 dimensional array, the size will be that
constexpr size_t ArraySize = (MaxN + 1) * MaxN / 2;

// To get the offset of a row of a Pascals Triangle stored in an1 1 dimensional array
constexpr size_t getTriangleNumber(size_t row) {
    size_t sum{};
    for (size_t i = 1; i <= row; i++)   sum += i;
    return sum;
}

// Generate a std::array with n elements of a given type and a generator function
template <typename DataType, DataType(*generator)(size_t), size_t... ManyIndices>
constexpr auto generateArray(std::integer_sequence<size_t, ManyIndices...>) {
    return std::array<DataType, sizeof...(ManyIndices)>{ { generator(ManyIndices)... } };
}
// This is a std::arrax<size_t,MaxN> withe the Name TriangleNumber, containing triangle numbers for ip ti MaxN
constexpr auto TriangleNumber = generateArray<size_t, getTriangleNumber>(std::make_integer_sequence<size_t, MaxN>());

// This function will generate Pascals triangle stored in an 1 dimension std::array
constexpr auto calculatePascalTriangle() {

    // Result of function. Here we will store Pascals triangle as an 1 dimensional array
    std::array <unsigned long long, ArraySize> pascalTriangle{};

    size_t index{}; // Running index for storing values in the array

    // Go through all rows and columns of Pascals triangle
    for (size_t row{}; row < MaxN; ++row) for (size_t col{}; col <= row; ++col) {

        // Border valuse are always one
        unsigned long long result{ 1 };
        if (col != 0 && col != row) {

            // So, we are not at the border. Get the start index the upper 2 values 
            const size_t offsetOfRowAbove = TriangleNumber[row - 1] + col;
            // And calculate the new value for the current row
            result = pascalTriangle[offsetOfRowAbove] + pascalTriangle[offsetOfRowAbove - 1];
        }
        // Store new value
        pascalTriangle[index++] = result;
    }
    // And return array as function result
    return pascalTriangle;
}
// This is a constexpr std::array<unsigned long long,ArraySize> with the name PPP, conatining all nCr results 
constexpr auto PPP = calculatePascalTriangle();

// To calculate nCr, we used look up the value from the array
constexpr unsigned long long nCr(size_t n, size_t r) {
    return PPP[TriangleNumber[n] + r];
}


// Some debug test driver code. Print Pascal triangle
int main() {
    constexpr size_t RowsToPrint = 16; // MaxN - 1;
    const size_t digits = static_cast<size_t>(std::ceil(std::log10(nCr(RowsToPrint, RowsToPrint / 2))));
    for (size_t row{}; row < RowsToPrint; ++row) {

        std::cout << std::string((RowsToPrint - row+1) * ((digits+1) / 2), ' ');

        for (size_t col{}; col <= row; ++col)
            std::cout << std::setw(digits) << nCr(row, col) << ' ';
        std::cout << '\n';
    }
    return 0;
}