Как мне ускорить эту программу для поиска последовательности Фибоначчи

#c #fibonacci #execution-time

#c #фибоначчи #время выполнения

Вопрос:

Я задаю этот вопрос о кодировании, в котором вас просят ввести числа N и M, и вы должны вывести N-е число Фибоначчи mod M. Мой код выполняется довольно медленно, и я хотел бы узнать, как его ускорить.

 #include<bits/stdc  .h> 
using namespace std; 

long long fib(long long N) 
{ 
    if (N <= 1) 
        return N; 
    return fib(N-1)   fib(N-2); 
} 

int main () 
{ 
    long long N;
    cin >> N;
    long long M;
    cin >> M;
    long long b;
    b = fib(N) % M;
    cout << b; 
    getchar(); 
    return 0; 
}
  

Комментарии:

1. динамическое программирование — чтобы не приходилось вычислять одни и те же числа снова и снова.

2. Смотрите Метод 2: geeksforgeeks.org/program-for-nth-fibonacci-number

3. Вы не ускоряете эту программу. Вы пишете совершенно другую программу.

Ответ №1:

Хотя программа, которую вы написали, в значительной степени является примером рекурсии в образовании, это действительно чертовски плохой алгоритм, как вы выяснили. Попробуйте записать дерево вызовов fib(7) , и вы обнаружите, что количество выполняемых вами вызовов резко возрастает.

Есть много способов ускорить ее и не допустить повторного вычисления одних и тех же значений снова и снова. Кто-то уже ссылался на кучу алгоритмов в комментариях — простой цикл может легко сделать его линейным в N вместо экспоненциального.

Однако одна из проблем заключается в том, что числа Фибоначчи растут довольно быстро: вы можете удерживать fib(93) 64-битное целое число, но fib(94) переполняет его.

Однако вам не нужно N-е число Фибоначчи — вам нужен N-й мод M. Это немного меняет задачу, потому что, пока M меньше MAX_INT_64 / 2, вы можете вычислять fib(N) mod M для любого N.

Обратите свое внимание на модульную арифметику и отношения соответствия. В частности, для добавления, в котором говорится (изменен синтаксис на C и немного упрощен):

Если a1 % m == b1 и a2 % m == b2 тогда (a1 a2) % m == (b1 b2) % m

Или, чтобы привести пример: 17 % 3 == 2 , 22 % 3 == 1 => (17 22) % 3 == (2 1) % 3 == 3 % 3 == 0

Это означает, что вы можете поместить оператор по модулю в середину вашего алгоритма, чтобы вы никогда не складывали большие числа вместе и никогда не переполнялись. Таким образом, вы можете легко вычислить, например. fib(10000) mod 237 .

Ответ №2:

Существует одна простая оптимизация при вызове fib без вычисления повторяющихся значений. Также использование циклов вместо рекурсии может ускорить процесс:

 int fib(int N) {
    int f0 = 0;
    int f1 = 1;
    for (int i = 0; i < N; i  ) {
         int tmp = f0   f1;
         f0 = f1;
         f1 = tmp;
    }
    return f1;
}
  

Поверх этого вы можете применить оператор по модулю, предложенный @Frodyne.

Ответ №3:

1-е наблюдение заключается в том, что вы можете превратить рекурсию в простой цикл:

 #include <cstdint>

std::uint64_t fib(std::uint16_t n) {
    if (!n)
        return 0;
    std::uint64_t result[]{ 0,1 };
    bool select = 1;
    for (auto i = 1; i < n;   i , select=!select)
    {
        result[!select]  = result[select];
    };
    return result[select];
};
  

затем вы можете запомнить его:

 #include <cstdint>
#include <vector>

std::uint64_t fib(std::uint16_t n) {
    static std::vector<std::uint64_t>  result{0,1};
    if (result.size()>n)
        return result[n];
    std::uint64_t back[]{ result.crbegin()[1],result.back() };
    bool select = 1;
    result.reserve(n   1);
    for (auto i=result.size(); i < result.capacity();  i, select = !select)
        result.push_back(back[!select]  = back[select]);
    return result[n];
};
  

Другим вариантом может быть алгебраическая формула.

приветствия, FM.