【问题标题】:What is the fastest method to find the largest Fibonacci-Number that fits in an int?找到适合 int 的最大斐波那契数的最快方法是什么?
【发布时间】:2020-03-14 11:22:43
【问题描述】:

我承认这个问题有点学术。但是,我相信该解决方案在 (C++) 数字方面显示出一些洞察力。

注意,第 N 个斐波那契数可以用递归计算

int Fibonacci(int N)
{
    if (N==1 || N==2) {
        return 1;
    }
    return Fibonacci(N-1) + Fibonacci(N-2);
}

对于这个问题,上述蛮力方法不是答案。这是因为 第 N 个斐波那契数可以通过以下方式非递归计算:

long int Fibonacci(int N)
{
    double num1 = pow((1+sqrt(5))/2.0,N);
    double num2 = pow((1-sqrt(5))/2.0,N);

    return (num1-num2)/sqrt(5);
}

【问题讨论】:

  • 最快的方法是预先计算答案,然后将其硬编码到代码中(最好分配给常数而不是幻数)。
  • @Eljay:是的,“我承认这个问题有点学术”。
  • 不只是学术,我经常这样做。我制作了一个小程序(在 Python 或 C++ 中)输出我嵌入到我的 C++ 代码中的预先计算的数据。这是您工具箱中的一项好技术。

标签: c++ algorithm numeric


【解决方案1】:

我复制了您的第一个示例,并放入了我多次使用过的函数。它使用数据而不是指令和简单的查找而不是计算。它有一个硬编码的斐波那契值表,适合 64 位整数。当然,如果只需要 32 位值,表格可以做得更小。

#include <iostream>
#include <limits.h>
#include <math.h>
#include <chrono>

long int Fibonacci(int n)
{
    static const long int seq[] = {
        0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,
        987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025,
        121393, 196418, 317811, 514229, 832040, 1346269, 2178309,
        3524578, 5702887, 9227465, 14930352, 24157817, 39088169,
        63245986, 102334155, 165580141, 267914296, 433494437, 701408733,
        1134903170, 1836311903, 2971215073, 4807526976, 7778742049,
        12586269025, 20365011074, 32951280099, 53316291173, 86267571272,
        139583862445, 225851433717, 365435296162, 591286729879,
        956722026041, 1548008755920, 2504730781961, 4052739537881,
        6557470319842, 10610209857723, 17167680177565, 27777890035288,
        44945570212853, 72723460248141, 117669030460994,
        190392490709135, 308061521170129, 498454011879264,
        806515533049393, 1304969544928657, 2111485077978050,
        3416454622906707, 5527939700884757, 8944394323791464,
        14472334024676221, 23416728348467685, 37889062373143906,
        61305790721611591, 99194853094755497, 160500643816367088,
        259695496911122585, 420196140727489673, 679891637638612258,
        1100087778366101931, 1779979416004714189, 2880067194370816120,
        4660046610375530309, 7540113804746346429
    };
    return n < int(sizeof seq / sizeof seq[0]) ? seq[n] : -1;
}

int main()
{
    auto start = std::chrono::system_clock::now();

    int IndexMax = 1;
    while (Fibonacci(IndexMax)<INT_MAX) 
    {
        ++IndexMax;
    }
    --IndexMax;
    auto end = std::chrono::system_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

    std::cout << "IndexMax = " << IndexMax << std::endl;
    std::cout << "Fibonacci_two(IndexMax): " << Fibonacci(IndexMax) << std::endl;
    std::cout << "calculation time: " << elapsed.count() << " microseconds" << std::endl;
}

这是Compiler Explorer 输出:

IndexMax = 46

Fibonacci_two(IndexMax): 1836311903

calculation time: 0 microseconds

它要快得多,我希望它不会违反问题的精神。它没有显示数字见解,但确实说明了代码与数据权衡的有用技术。

【讨论】:

    【解决方案2】:

    方法一(不是最快的):

    使用非递归方法计算第 N 个斐波那契数 可以使用以下代码找到适合 int 的最大斐波那契数:

    #include <iostream>
    #include <limits.h>
    #include <math.h>
    #include <chrono>
    
    long int Fibonacci(int n)
    {
        double num1 = pow((1+sqrt(5))/2.0,n);
        double num2 = pow((1-sqrt(5))/2.0,n);
    
        return (num1-num2)/sqrt(5);
    }
     
    int main()
    {
        auto start = std::chrono::system_clock::now();
    
        int IndexMax = 1;
        while (Fibonacci(IndexMax)<INT_MAX) 
        {
            ++IndexMax;
        }
        --IndexMax;
        auto end = std::chrono::system_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
        
        std::cout << "IndexMax = " << IndexMax << std::endl;
        std::cout << "Fibonacci_two(IndexMax): " << Fibonacci(IndexMax) << std::endl;
        std::cout << "calculation time: " << elapsed.count() << " microseconds" << std::endl;
    }
    

    你可以run the code for method 1 online看到如下输出:

    IndexMax = 46
    Fibonacci_two(IndexMax): 1836311903
    calculation time: 33 microseconds
    

    但还有一种更快的方法:

    方法二:

    根据Wikipedia,可以反转第 N 个斐波那契数的显式公式。通过这个技巧,我们可以实现更快的方法:

    #include <iostream>
    #include <limits.h>
    #include <math.h>
    #include <chrono>
    
    long int Fibonacci(int n)
    {
        double num1 = pow((1+sqrt(5))/2.0,n);
        double num2 = pow((1-sqrt(5))/2.0,n);
    
        return (num1-num2)/sqrt(5);
    }
    
    double Fibonacci_invert(double Fn)
    {
        double num1 = Fn*sqrt(5.0);
        double num2 = sqrt(5.0*Fn*Fn+4.0);
        double phi = (1.0 + sqrt(5.0))/2.0;
    
        return round(log((num1+num2)/2.0)/log(phi));
    }
    
    int main()
    {
        auto start = std::chrono::system_clock::now();
    
        int IndexMax = Fibonacci_invert(INT_MAX);
        int FibonacciMax = Fibonacci(IndexMax);
    
        auto end = std::chrono::system_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
        
        std::cout << "IndexMax: " << IndexMax << std::endl;
        std::cout << "FibonacciMax: " << FibonacciMax << std::endl;
        std::cout << "calculation time: " << elapsed.count() << " microseconds" << std::endl;
    }
    

    感谢Eric Postpischil,请注意Fibonacci_Invert 通常不是找到不大于其参数的最大斐波那契数的正确函数。

    你可以run the code for method 2 online看到如下输出:

    IndexMax: 46
    FibonacciMax: 1836311903
    calculation time: 23 microseconds
    

    【讨论】:

    • 需要注意的是,Fibonacci_invert 可能适用于像INT_MAX 这样的值,它们是各种整数类型在合理范围内的最大值,但它通常不是找到最大值的正确函数斐波那契数不大于其参数。这是因为在将其参数转换为其参数以及其中几乎所有操作中都存在舍入错误。使用 Apple Clang 11.0.0 编译并在 macOS 10.14.6 上执行,它为输入 8 生成索引 5,对应于斐波那契数 5。同样,它对于 144、377、2584、46368 等来说太低了。跨度>
    • @Eric Postpischil:我修复了代码。问题是函数Fibonacci_invert 中缺少round
    • 尝试通过圆整来修复浮点错误通常不是一个好主意。在这种情况下,它打破了斐波那契数 2(为斐波那契数 3 生成索引 4)和 3(为 5 生成 5)的例程。正如我所指出的,该例程适用于合理范围内的整数类型最大值。这就是问题所要求的,所以这就足够了。应该注意的是,它通常不适用于其他值。为所有整数制定一个完整的解决方案可能并不值得。
    • 在范围内,我可能预计 Fibonacci_invert 最多偏离 1,取决于所使用的浮点类型。因此,通过使用Fibonacci 来测试其初始答案以获取相应的数字并将其与Fibonacci_invert 的输入进行比较可能足以在范围内纠正。
    • @Eric Postpischil:非常感谢您的 cmets!我在答案中添加了适当的注释。
    猜你喜欢
    • 1970-01-01
    • 2011-11-15
    • 1970-01-01
    • 2019-06-26
    • 2017-04-02
    • 2016-11-28
    • 2020-05-24
    • 2012-12-29
    • 2014-02-22
    相关资源
    最近更新 更多