C 中的fibonacci Memoization算法

Fibonacci memoization algorithm in C++

本文关键字:算法 Memoization fibonacci 中的      更新时间:2023-10-16

我在动态编程中有点挣扎。更具体地,实施一种用于查找fibonacci数字n的算法。

我有一种可行的天真算法:

int fib(int n) {
    if(n <= 1)
        return n;
    return fib(n-1) + fib(n-2);
}

但是,当我尝试使用回忆进行操作时,功能总是返回0:

int fib_mem(int n) {
    if(lookup_table[n] == NIL) {
        if(n <= 1)
            lookup_table[n] = n;
        else
            lookup_table[n] = fib_mem(n-1) + fib_mem(n-2);
    }
    return lookup_table[n];
}

我已经定义了Lookup_table,最初存储在所有元素中。

有什么想法可能是什么问题?

这是按要求的整个程序:

#include <iostream>
#define NIL -1
#define MAX 100
long int lookup_table[MAX];
using namespace std;
int fib(int n);
int fib_mem(int n);
void initialize() {
    for(int i = 0; i < MAX; i++) {
        lookup_table[i] == NIL;
    }
}
int main() {
    int n;
    long int fibonnaci, fibonacci_mem;
    cin >> n;
    // naive solution
    fibonnaci = fib(n);
    // memoized solution
    initialize();
    fibonacci_mem = fib_mem(n);
    cout << fibonnaci << endl << fibonacci_mem << endl;
    return 0;
}
int fib(int n) {
    if(n <= 1)
        return n;
    return fib(n-1) + fib(n-2);
}
int fib_mem(int n) {
    if(lookup_table[n] == NIL) {
        if(n <= 1)
            lookup_table[n] = n;
        else
            lookup_table[n] = fib_mem(n-1) + fib_mem(n-2);
    }
    return lookup_table[n];
}

我倾向于通过将幼稚的实现与回忆混合来找到最简单的写作方法:

int fib_mem(int n);
int fib(int n) { return n <= 1 ? n : fib_mem(n-1) + fib_mem(n-2); }
int fib_mem(int n)
{
    if (lookup_table[n] == NIL) {
        lookup_table[n] = fib(n);
    }
    return lookup_table[n];
}
#include <iostream>
#define N 100
using namespace std;
const int NIL = -1;
int lookup_table[N];
void init()
{
    for(int i=0; i<N; i++)
        lookup_table[i] = NIL;
}
int fib_mem(int n) {
    if(lookup_table[n] == NIL) {
        if(n <= 1)
            lookup_table[n] = n;
        else
            lookup_table[n] = fib_mem(n-1) + fib_mem(n-2);
    }
    return lookup_table[n];
}
int main()
{
    init();
    cout<<fib_mem(5);
    cout<<fib_mem(7);
}

使用完全相同的函数,这很好。

您在lookup_table的初始化中做错了什么。

由于问题是初始化,因此C 标准库允许您初始化序列而不必编写for循环,从而阻止您犯错误,例如使用==而不是=。p> std :: fill_n函数这样做:

#include <algorithm>
//...
void initialize()
{
   std::fill_n(lookup_table, MAX, NIL);
}

有趣的概念。通过回忆加速。

有一个不同的概念。您可以称其为编译时间纪念。但实际上,这是所有适合64位值的斐波那契数的编译时间预算。

斐波那契序列的一个重要特性是这些值的增长很强。因此,整数数据类型中的所有现有构建都将相当快地溢出。

使用BINET的公式,您可以计算出第93 fibonacci编号是适合64位无签名值的最后一个。

和在编译过程中计算93个值是一个非常简单的任务。

我们将首先定义计算斐波那契号的默认方法为 constexpr函数:

// Constexpr function to calculate the nth Fibonacci number
constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
    // Initialize first two even numbers 
    unsigned long long f1{ 0 }, f2{ 1 };
    // calculating Fibonacci value 
    while (index--) {
        // get next value of Fibonacci sequence 
        unsigned long long f3 = f2 + f1;
        // Move to next number
        f1 = f2;
        f2 = f3;
    }
    return f2;
}

这样,可以在运行时轻松计算斐波那契数。然后,我们用所有斐波那契号填充std::array。我们还使用constexpr,并将其制成带有variadic参数包的模板。

我们使用 std::integer_sequence创建一个索引fibonacci编号0,1,2,3,4,5,....

straigtforward且不复杂:

template <size_t... ManyIndices>
constexpr auto generateArrayHelper(std::integer_sequence<size_t, ManyIndices...>) noexcept {
    return std::array<unsigned long long, sizeof...(ManyIndices)>{ { getFibonacciNumber(ManyIndices)... } };
};

此功能将用整数序列为0,1,2,3,4,...并返回带有相应fibonacci编号的std::array<unsigned long long, ...>

我们知道我们可以存储最大93个值。因此,我们将发出下一个功能,它将用整数序列1,2,3,4,...,92,93称为上述功能,就像这样:

constexpr auto generateArray() noexcept {
    return generateArrayHelper(std::make_integer_sequence<size_t, MaxIndexFor64BitValue>());
}

现在,最后,

constexpr auto FIB = generateArray();

将为我们提供一个编译时std::array<unsigned long long, 93>,其中包含所有fibonacci编号的名称fib。而且,如果我们需要fibonacci编号,那么我们可以简单地编写FIB[i]。运行时不会计算。

我认为没有更快的方法来计算fibonacci编号。

请参阅下面的完整程序:

#include <iostream>
#include <array>
#include <utility>
// ----------------------------------------------------------------------
// All the following will be done during compile time
// Constexpr function to calculate the nth Fibonacci number
constexpr unsigned long long getFibonacciNumber(size_t index) {
    // Initialize first two even numbers 
    unsigned long long f1{ 0 }, f2{ 1 };
    // calculating Fibonacci value 
    while (index--) {
        // get next value of Fibonacci sequence 
        unsigned long long f3 = f2 + f1;
        // Move to next number
        f1 = f2;
        f2 = f3;
    }
    return f2;
}
// We will automatically build an array of Fibonacci numberscompile time
// Generate a std::array with n elements 
template <size_t... ManyIndices>
constexpr auto generateArrayHelper(std::integer_sequence<size_t, ManyIndices...>) noexcept {
    return std::array<unsigned long long, sizeof...(ManyIndices)>{ { getFibonacciNumber(ManyIndices)... } };
};
// Max index for Fibonaccis that for in an 64bit unsigned value (Binets formula)
constexpr size_t MaxIndexFor64BitValue = 93;
// Generate the required number of elements
constexpr auto generateArray()noexcept {
    return generateArrayHelper(std::make_integer_sequence<size_t, MaxIndexFor64BitValue>());
}
// This is an constexpr array of all Fibonacci numbers
constexpr auto FIB = generateArray();
// ----------------------------------------------------------------------
// Test
int main() {
    // Print all possible Fibonacci numbers
    for (size_t i{}; i < MaxIndexFor64BitValue; ++i)
        std::cout << i << "t--> " << FIB[i] << 'n';
    return 0;
}

通过Microsoft Visual Studio社区2019,版本16.8.2。

开发和测试

另外用Clang11.0和GCC10.2

进行了编译和测试。

语言:C 17

initialize()函数中存在错误:

void initialize() {
    for(int i = 0; i < MAX; i++) {
        lookup_table[i] == NIL; // <- mistake
    }
}

在指向的行中,您比较lookup_table[i]NIL(并且不要使用结果),而不是将NIL分配给lookup_table[i]

进行分配,您应该使用=而不是==

此外,在这种情况下,最正确的做法是启用所有警告的程序。例如,MS VC 显示以下警告:

warning C4553: '==': operator has no effect; did you intend '='?

错误是在初始化函数上(您使用了比较操作员'==',其中想要归因运算符'=')。但是,在语义上,您不需要使用-1(nil)初始化look_table,因为斐波那契的结果永远不会为0(零);因此,您可以用零初始化所有内容。在最终解决方案下方看:

#include <iostream>
#define NIL 0
#define MAX 1000
long int lookup_table[MAX] = {};
using namespace std;
long int fib(int n) {
    if(n <= 1)
        return n;
    return fib(n-1) + fib(n-2);
}
long int fib_mem(int n) {
    assert(n < MAX);
    if(lookup_table[n] == NIL) {
        if(n <= 1)
            lookup_table[n] = n;
        else
            lookup_table[n] = fib_mem(n-1) + fib_mem(n-2);
    }
    return lookup_table[n];
}
int main() {
    int n;
    long int fibonnaci, fibonacci_mem;
    cout << " n = "; cin >> n;
    // naive solution
    fibonnaci = fib(n);
    // memoized solution
    // initialize();
    fibonacci_mem = fib_mem(n);
    cout << fibonnaci << endl << fibonacci_mem << endl;
    return 0;
}

在我的解决方案中,我正在使用地图而不是数组来存储备忘录。

#include <iostream>
#include <map>
using namespace std;
map<int, unsigned long long> memo;
unsigned long long fibo(int n) {
    if (memo[n]) {
        return memo[n];
    }
    if (n <= 1) {
        return n;
    }
    memo[n] = fibo(n-1) + fibo(n-2);
    return memo[n];
}
int main() {
    int n; 
    cin >> n;
    cout << "Ans: " << fibo(n);
    return 0;
}