【问题标题】:Addition of Even Fibonacci Numbers偶数斐波那契数的加法
【发布时间】:2020-01-15 23:40:58
【问题描述】:

我正在尝试解决 Project Euler 上的2nd problem,我必须打印所有低于 400 万的偶数斐波那契数的总和。我正在使用以下代码,但程序没有返回任何值。当我用 10 之类的小东西替换 4000000 时,我得到了总和。这是否意味着我的程序耗时太长?我做错了什么?

#include <iostream>
using namespace std;

int fibonacci(int i) {
    if (i == 2)
        return 2;
    else if (i == 1)
        return 1;
    else return fibonacci(i - 1) + fibonacci(i - 2);
}

int main() {

    int currentTerm, sum = 0;
    for (int i = 1; i <= 10; i++) {

        currentTerm = fibonacci(i);
        if (currentTerm % 2 == 0)
            sum += currentTerm;
    }
    cout << sum;
    return 0;
}

【问题讨论】:

  • 尝试在此处使用unsigned long long int 代替intint currentTerm, sum = 0; 并从您的fibonacci 函数返回unsigned long long int
  • 为什么你们都使用循环和递归?如果您循环 4000000 次,那么当 i 具有更高的值时,您也会进行大量的递归(这意味着 4000000 次循环迭代,此外每次循环迭代还有大约 i 个递归调用)
  • 我的第一个猜测是堆栈溢出。您的斐波那契算法正在递归地使用堆栈进行计算。此外,当您重复相同的计算时,运行时复杂性是指数级的。我建议你找到一种方法来在线性时间内计算任何斐波那契数(不是那么难),然后在这个新算法中计算总和。
  • 请注意,他的问题表明他只需要添加小于 400 万次的数字,而不是 小于 400 万次迭代

标签: c++ fibonacci


【解决方案1】:

Problem 2 项目欧拉问(强调我的)

通过考虑斐波那契数列中不超过四百万的,求偶值项的总和。

在做

for (int i = 1; i <= 4000000; i++)
{
     currentTerm = fibonacci(i);
     // ...
}

您正在尝试计算高达 4,000,000th 斐波那契数,这是一个非常大的野兽,而您应该改为在 33th 附近停止。

其他答案已经指出了递归方法的低效率,但让我在讨论中添加一些数字,使用这个稍微修改过的程序版本

#include <iostream>
#include <iomanip>

int k = 0;

// From https://oeis.org/A000045 The fibonacci numbers are defined by the
// recurrence relation F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1.
// In the project Euler question the sequence starts with 1, 2, 3, 5, ...
// So in the following I'll consider F(1) = 1 and F(2) = 2 as The OP does.
long long fibonacci(long long i)
{
    ++k;
    if (i > 2)
        return fibonacci(i - 1) + fibonacci(i - 2);
    else
        return i;
}

int main()
{
    using std::cout;
    using std::setw;
    const long limit = 4'000'000;
    long sum = 0;

    cout << "  i      F(i)       sum      calls\n"
            "-----------------------------------\n";
    for (int i = 1; ; ++i)
    {
        long long F_i = fibonacci(i);

        if ( F_i > limit )             // <-- corrected end condition
            break;

        if (F_i % 2 == 0)
        {
            sum += F_i;

            cout << setw(3) << i << setw(10) << F_i
                 << setw(10) << sum << setw(11) << k << '\n';
        }
    }

    cout << "\nThe sum of all even Fibonacci numbers less then "
         << limit << " is " << sum << '\n';
    return 0;
}

执行后(实时here),您会注意到递归函数已被调用超过 10,000,000 次,以计算最多 33th 斐波那契数.

这根本不是正确的方法。记忆化可能会有所帮助,here 有一个快速基准,将递归函数与记忆化技术的玩具实现进行比较,它由您看不到的直方图表示。因为它比其他的短 300,000 倍。

不过,这不是处理这个问题的“正确”或“自然”方式。如其他答案中所述,您可以简单地按顺序计算每个数字,给定前面的数字。 Enthus3d 还注意到序列中的模式:奇数、奇数、偶数、奇数、奇数、偶数、...

我们可以更进一步,直接计算偶数项:

#include <iostream>

int main()
{
    const long limit = 4'000'000;

    // In the linked question the sequence starts as 1, 2, 3, 5, 8, ... 
    long long F_0 = 2, F_3 = 8, sum = F_0 + F_3;

    for (;;)
    {
        // F(n+2) = F(n+1) + F(n)
        // F(n+3) = F(n+2) + F(n+1) = F(n+1) + F(n) + F(n+1) = 2F(n+1) + F(n)
        // F(n+6) = F(n+5) + F(n+4) = F(n+4) + F(n+3) + F(n+3) + F(n+2)
        //        = 2F(n+3) + F(n+4) + F(n+2) = 3F(n+3) + 2F(n+2)
        //        = 3F(n+3) + 2F(n+1) + 2F(n) = 3F(n+3) + F(n+3) - F(n) + 2F(n)
        long long F_6 = 4 * F_3 + F_0;

        if ( F_6 > limit )
            break;

        sum += F_6;
        F_0 = F_3;
        F_3 = F_6;
    }

    std::cout << sum << '\n';    // --> 4613732
    return 0;
}

直播here.

【讨论】:

  • 太棒了!这是这里所有答案中最完整的答案,您可以根据实际的欧拉问题陈述来实现逻辑。我希望 OP 先看到你的答案。
【解决方案2】:

如果您需要多个斐波那契数字,尤其是如果您需要所有这些数字,请不要使用递归方法,而是使用迭代:

var prev=0;
var curr=1;
var sum=0;
while(curr<4000000){
  if(curr%2==0)
    sum+=curr;
  var temp=prev;
  prev=curr;
  curr+=temp;
}
console.log(sum);

sn-p 是 JavaScript(所以它可以在这里运行),但如果你将 var-s 设置为 int-s,它就足够 C-ish。

但实际的问题是循环:你不需要计算第一个 n (4000000) 斐波那契数(会导致各种溢出),但小于 4000000 的斐波那契数。

【讨论】:

    【解决方案3】:

    如果你想要一点魔法,你也可以建立在每个第三个斐波那契数都是偶数的基础上,基于“偶数+奇数=>奇数”,“奇数+偶数=>奇数”,并且只有“奇+奇=>偶”:

    0 1 1 2 3 5 8...
    E O O E O O E
                ^ O+O
              ^ E+O
            ^ O+E
          ^ O+O
    

    var prev=1;
    var curr=2;
    var sum=0;
    while(curr<4000000){
      sum+=curr;
      console.log("elem: "+curr,"sum: "+sum);
      for(var i=0;i<3;i++){
        var temp=prev;
        prev=curr;
        curr+=temp;
      }
    }


    如果问题只是标题,偶数斐波那契数的加法(比如说,n 个),纯数学可以使用比内公式(描述在@Silerus 的answer) 中,它是(a^n-b^n)/c 的东西,其中a^nb^n 是几何序列,其中每3 个也是几何序列(a^3)^n 和@987654322 @ 有一个简单的封闭形式(如果系列是 a*r^n,则总和是 a*(1-r^n)/(1-r))。

    把所有东西放在一起:

    // convenience for JS->C
    var pow=Math.pow;
    var sqrt=Math.sqrt;
    var round=Math.round;
    
    var s5=sqrt(5);
    var a=(1+s5)/2;
    var a3=pow(a,3);
    var b=(1-s5)/2;
    var b3=pow(b,3);
    for(var i=0;i<12;i++){
      var nthEvenFib=round((pow(a3,i)-pow(b3,i))/s5);
      var sumEvenFibs=round(((1-pow(a3,i+1))/(1-a3)-(1-pow(b3,i+1))/(1-b3))/s5);
      console.log("elem: "+nthEvenFib,"sum: "+sumEvenFibs);
    }

    同样,如果var-s 被一些 C 类型替换,则两个 sn-ps 都变得相当 C-ish,int-s 在第一个 sn-p 中,大部分 double-s 在后者中一个(循环变量i当然可以是一个简单的int)。

    【讨论】:

      【解决方案4】:

      您可以在计算中使用 Binet 公式 - 这将允许您放弃慢速递归算法,另一种选择可能是计算斐波那契数的非递归算法。 https://en.wikipedia.org/wiki/Jacques_Philippe_Marie_Binet。这是一个使用比内公式的例子,它比递归算法快得多,因为它不会重新计算所有以前的数字。

      #include <iostream>
      #include <math.h>
      using namespace std;
      int main(){
          double num{},a{(1+sqrt(5))/2},b{(1-sqrt(5))/2},c{sqrt(5)};
          int sum{};
          for (auto i=1;i<30;++i){
              num=(pow(a,i)-pow(b,i))/c;
              if (static_cast<int>(num)%2==0)
                  sum+=static_cast<int>(num);
          }
          cout<<sum;
      return 0;
      }
      

      变体 2

      int fib_sum(int n)
      {
          int sum{};
          if (n <= 2) return 0;
          std::vector<int> dp(n + 1);
          dp[1] = 1; dp[2] = 1;
          for (int i = 3; i <= n; i++)
          {
             dp[i] = dp[i - 1] + dp[i - 2];
             if(dp[i]%2==0) 
               sum+=dp[i];
          }
          return sum;
      }
      

      【讨论】:

      • 嗨,Silerus,最好提供具体的例子来说明您何时给出答案,而不是链接到其他网站。另外,请务必阅读问题,以确保您的答案是相关的。
      • 对不起,我认为错误的算法是正确的答案,并为正确的解决方案提出了另一种算法,现在我将使用比内公式编写一个解决方案,并不难
      • 太好了,我等着你的回答。
      • 这是一个很好的例子,但是 OP 询问我们如何修复他的代码,您的回答与他的问题无关。它也许可以改善他的运行时间,但这不是一个完整的答案。
      • 你能做加法吗?虽然这种重量级的数学无法与简单的求和相媲美,但我发现它是一个有趣的起点 (stackoverflow.com/a/57943985/7916438),如果它符合 OP 的要求(即计算总和),我想投票赞成。跨度>
      【解决方案5】:

      您可以通过使用 constexpre 函数对所有偶数斐波那契数和总和使用编译时预计算来大幅加快速度。

      对 Binets 公式的简短检查表明,30 个偶数斐波那契数将适合 64 位无符号值。

      30 个数字可以很容易地计算出来,而无需编译器付出任何努力。因此,我们可以创建一个包含所有需要值的编译时间constexpr std::array

      因此,您将拥有零运行时开销,从而使您的编程速度非常快。我不确定是否有更快的解决方案。请看:

      #include <iostream>
      #include <array>
      #include <algorithm>
      #include <iterator>
      
      
      // ----------------------------------------------------------------------
      // All the following wioll be done during compile time
      // Constexpr function to calculate the nth even Fibonacci number
      constexpr unsigned long long getEvenFibonacciNumber(size_t index) {
          // Initialize first two even numbers 
          unsigned long long f1{ 0 }, f2{ 2 };
      
          // calculating Fibonacci value 
          while (--index) {
              // get next even value of Fibonacci sequence 
              unsigned long long f3 = 4 * f2 + f1;
      
              // Move to next even number
              f1 = f2;
              f2 = f3;
          }
          return f2;
      }
      
      // Get nth even sum of Fibonacci numbers
      constexpr unsigned long long getSumForEvenFibonacci(size_t index) {
          // Initialize first two even prime numbers 
          // and their sum 
          unsigned long long f1{ 0 }, f2{ 2 }, sum{ 2 };
      
          // calculating sum of even Fibonacci value 
          while (--index) {
              // get next even value of Fibonacci sequence 
              unsigned long long f3 = 4 * f2 + f1;
      
              // Move to next even number and update sum 
              f1 = f2;
              f2 = f3;
              sum += f2;
          }
          return sum;
      }
      
      // Here we will store ven Fibonacci numbers and their respective sums
      struct SumOfEvenFib {
          unsigned long long fibNum;
          unsigned long long sum;
          friend bool operator < (const unsigned long long& v, const SumOfEvenFib& f) { return v < f.fibNum; }
      };
      
      // We will automatically build an array of even numbers and sums during compile time
      // Generate a std::array with n elements taht consist of const char *, pointing to Textx...Texty
      template <size_t... ManyIndices>
      constexpr auto generateArrayHelper(std::integer_sequence<size_t, ManyIndices...>) noexcept {
          return std::array<SumOfEvenFib, sizeof...(ManyIndices)>{ { {getEvenFibonacciNumber(ManyIndices + 1), getSumForEvenFibonacci(ManyIndices + 1)}...}};
      };
      
      // You may check with Ninets formula
      constexpr size_t MaxIndexFor64BitValue = 30;
      
      // Generate the reuired number of texts
      constexpr auto generateArray()noexcept {
          return generateArrayHelper(std::make_integer_sequence<size_t, MaxIndexFor64BitValue>());
      }
      
      // This is an constexpr array of even Fibonacci numbers and its sums
      constexpr auto SOEF = generateArray();
      // ----------------------------------------------------------------------
      
      
      int main() {
      
      
          // Show sum for 4000000
          std::cout << std::prev(std::upper_bound(SOEF.begin(), SOEF.end(), 4000000))->sum << '\n';
      
          // Show all even numbers and their corresponding sums
          for (const auto& [even, sum] : SOEF) std::cout << even << " --> " << sum << '\n';
          
          return 0;
      }
      

      使用 MSVC 19、clang 11 和 gcc10 测试

      用 C++17 编译

      【讨论】:

        【解决方案6】:

        欢迎来到 Stack Overflow :)

        我只在循环中修改了您的代码,并保持您的斐波那契实现不变。我已经在 Project Euler 上验证了代码的答案。代码可以在下面找到,希望我的cmets能帮助你更好的理解。

        我改变的三件事是:

        1) 您一直试图寻找一个数字,直到 4,000,000 迭代,而不是小于 4,000,000 的数字。这意味着您的程序可能会疯狂地尝试添加一个非常大的数字(我们不需要)这可能就是您的程序放弃的原因

        2) 我改进了偶数检查;我们知道斐波那契数列是奇奇偶,奇奇偶,所以我们只需要将每三个数字加到我们的总和中,而不是检查数字本身是否是偶数 模运算非常昂贵大数

        3)我添加了两行用 couts 注释掉的行,它们可以帮助您调试和排除输出故障

        还有一个链接here 使用动态编程更有效地解决问题,如果有人需要的话。

        祝你好运!

        #include <iostream>
        using namespace std;
        
        int fibonacci(int i) {
            if (i == 2)
                return 2;
            else if (i == 1)
                return 1;
            else return fibonacci(i - 1) + fibonacci(i - 2);
        }
        
        int main() {
            // need to add the sum of all even fib numbers under a particular sum
            int max_fib_number = 4000000;
            int currentTerm, sum = 0;
            currentTerm = 1;
            int i = 1;
        
            // we do not need a for loop, we need a while loop
            // this is so we can detect when our current number exceeds fib
            while(currentTerm < max_fib_number) {
                currentTerm = fibonacci(i);
                //cout << currentTerm <<"\n";
        
                // notice we check here if currentTerm is a valid number to add
                if (currentTerm < max_fib_number) {
                    //cout << "i:" << i<< "\n";
        
                    // we only want every third term
                    // this is because 1 1 2, 3 5 8, 13 21 34,
                    // pattern caused by (odd+odd=even, odd+even=odd)
                    // we also add 1 because we start with the 0th term
                    if ((i+1) % 3 == 0)
                        sum += currentTerm;
                }
                i++;        
            } 
            cout << sum;
            return 0;
        }
        

        【讨论】:

        • 就时间而言,这是次优的解决方案。你一遍又一遍地计算相同的数字。这可以通过在计算斐波那契数时使用记忆技术来避免。执行速度应该会显着提高。
        • 他只增加了 400 万。我的答案在几分之一秒内完成。
        • 我只是想为他提供一个他可以轻松理解的答案,只需很少的改动。如果我们进行太​​多更改,可能会给每个人带来困惑,并且在帮助用户理解解决问题的本质方面会适得其反。
        • 正确。但 400 万是私人案例,其他读者可能希望将算法应用于更大的数字。
        • 确实如此,但这里需要注意的是,我们的问题仅适用于偶数,并且是为了理解他自己的程序中的错误而提出的。但是,我明白您为其他用户提供服务的意义,因此我将搜索堆栈溢出,看看是否有更好的实现并将它们链接到我的答案中
        【解决方案7】:

        这是您修改后的代码,可以为项目欧拉问题产生正确的输出。

        #include <iostream>
        using namespace std;
        
        int fibonacci(int i) {
            if (i == 2)
                return 2;
            else if (i == 1)
                return 1;
            else return fibonacci(i - 1) + fibonacci(i - 2);
        }
        
        int main() {
            int currentsum, sum = 0;
            for (int i = 1; i <= 100; i++) {
                currentsum = fibonacci(i);
                //here's where you doing wrong 
                if(sum >= 4000000) break; //break when sum reaches 4mil
                if(currentsum %2 == 0) sum+=currentsum;  // add when even-valued occurs in the currentsum
            }
        
            cout << sum;
            return 0;
        }
        

        输出 4613732

        这是我的代码,它由 while 循环组成,直到总和出现 400 万,并带有一些解释。

        #include <iostream>
        using namespace std;
        
        int main()
        {
            unsigned long long int a,b,c , totalsum;
            totalsum = 0;
            a = 1; // 1st index digit in fib series(according to question)
            b = 2; // 2nd index digit in fib series(according to question)
            totalsum+=2; // because 2 is an even-valued term in the series 
           while(totalsum < 4000000){ //loop until 4million
                c = a+b; // add previous two nums
                a = b;  
                b = c;  
               if(c&1) continue; // if its odd ignore and if its an even-valued term add to totalsum
               else totalsum+=c; 
            }
            cout << totalsum;
            return 0;
        }
        

        对于投反对票的人,您实际上可以说出代码中的问题,而不是反对https://projecteuler.net/problem=2 的实际答案是上述代码 4613732 的输出,竞争性编程本身就是关于如何快速你能解决问题而不是干净的代码。

        【讨论】:

        • 如果他需要一个迭代器 > 100 的输入,你的程序就不能工作。
        • 你读过欧拉项目的问题吗? @热情3d。 fibonnaci 系列产生前两个数字的总和,呈指数增长,并且 400 万发生在该系列的 35 次迭代以下
        • 我只是认为为 for 循环做出假设而不是仅使用 while 循环是不优雅的。
        • 是的,我知道他只需要将它增加到 400 万。
        • 是的,一分钟后,我会添加我的解决方案,这与您所说的完美契合
        猜你喜欢
        • 2015-07-25
        • 1970-01-01
        • 2021-12-21
        • 2011-02-04
        • 1970-01-01
        • 2016-04-07
        • 2020-01-18
        • 1970-01-01
        • 2015-11-12
        相关资源
        最近更新 更多