【问题标题】:How to generate Fibonacci faster [duplicate]如何更快地生成斐波那契
【发布时间】:2011-03-21 05:30:28
【问题描述】:

我是一名 CSE 学生,正在为编程比赛做准备。现在我正在研究斐波那契数列。我有一个大小约为一些包含正整数的 Kilo 字节的输入文件。输入格式看起来像

3 5 6 7 8 0

零表示文件结束。输出应该像

2 
5 
8 
13 
21 

我的代码是

#include<stdio.h>

int fibonacci(int n) {
  if (n==1 || n==2)
    return 1;
  else
    return fibonacci(n-1) +fibonacci(n-2);
}
int main() {
  int z;
  FILE * fp;    
  fp = fopen ("input.txt","r");    
  while(fscanf(fp,"%d", &z) && z) 
   printf("%d \n",fibonacci(z));
  return 0;
}

该代码适用于样本输入并提供准确的结果,但问题是对于我的真实输入集,它花费的时间超过了我的时间限制。谁能帮帮我。

【问题讨论】:

    标签: c algorithm fibonacci


    【解决方案1】:

    您也可以使用快速倍增法生成斐波那契数列 链接:fastest-way-to-compute-fibonacci-number

    其实是从矩阵求幂法的结果推导出来的。

    【讨论】:

      【解决方案2】:

      如果内存有限制,您可以简单地使用函数的尾递归版本,该函数返回最后两个斐波那契数。

      int fib(int n)
      {
          int a = 0;
          int b = 1;
          while (n-- > 1) {
              int t = a;
              a = b;
              b += t;
          }
          return b;
      }
      

      这是O(n),需要一个固定的空格。

      【讨论】:

      • +1 为简单起见。这也可以很好地扩展到一个简单的 bigint 实现,其中 ab 是数组......您甚至可以将它们设为 base-1000000000 bigint,因此一旦完成就很容易打印结果。
      • 只是为了增加我的 2 美分,这是一种使用其他人已经提到的记忆技术的自下而上的方法。该技术是一种动态编程技术:en.wikipedia.org/wiki/Dynamic_programming
      • 仅对于那些以 (0, 1, 1, 2, 3, 5...) 开头的序列,您可以设置 a = 1, b = 0,并将 while 循环设置为 (n --> 1)。感谢您提供这个很棒的代码 Kru !!!
      • 看看n-- &gt;= 2不是更好吗?并使用 long 而不是 int?
      【解决方案3】:

      没有人提到 2 值堆栈数组版本,所以我只是为了完整起见。

      // do not call with i == 0
      uint64_t Fibonacci(uint64_t i)
      {
        // we'll only use two values on stack,
        // initialized with F(1) and F(2)
        uint64_t a[2] = {1, 1};
      
        // We do not enter loop if initial i was 1 or 2 
        while (i-- > 2)
          // A bitwise AND allows switching the storing of the new value
          // from index 0 to index 1.
          a[i & 1] = a[0] + a[1];
      
          // since the last value of i was 0 (decrementing i),
          // the return value is always in a[0 & 1] => a[0].
        return a[0];
      }                                                                
      

      这是一个 O(n) 的常量堆栈空间解决方案,在使用优化编译时,其性能与记忆化稍有相同。

      // Calc of fibonacci f(99), gcc -O2
      Benchmark            Time(ns)    CPU(ns) Iterations
      BM_2stack/99                2          2  416666667
      BM_memoization/99           2          2  318181818
      

      这里使用的 BM_memoization 将只初始化数组一次,并在其他所有调用中重复使用它。

      2 值堆栈数组版本在优化时与带有临时变量的版本执行相同。

      【讨论】:

      • 问题阅读Can anyone help me [meet my time limit]?。由于呈现的原始递归代码太慢,请争论您的提议如何更快,或提供测量机制和结果。
      • 在优化时添加了一个针对记忆的基准。
      • 当我们这样做时,您甚至可以通过使用前减量 (--i) 而不是后减量 ('i--') 来进一步减少执行,从而减少一条指令的执行.右行“while (i-- > 2)”生成这些指令:movl %eax, %ecx; addl $-1, %ecx。如果将其更改为while (--i &gt; 1),它将仅生成addl $-1, %eax,删除一条指令。超级微小的细节,但在我们进行微优化时,我想提一下这个
      【解决方案4】:

      矩阵乘法,无浮点运算,O(log N) 时间复杂度假设整数乘法/加法在恒定时间内完成。

      这里是python代码

      def fib(n):
          x,y = 1,1
          mat = [1,1,1,0]
          n -= 1
          while n>0:
              if n&1==1:
                  x,y = x*mat[0]+y*mat[1], x*mat[2]+y*mat[3]
              n >>= 1
              mat[0], mat[1], mat[2], mat[3] = mat[0]*mat[0]+mat[1]*mat[2], mat[0]*mat[1]+mat[1]*mat[3], mat[0]*mat[2]+mat[2]*mat[3], mat[1]*mat[2]+mat[3]*mat[3]
          return x
      

      【讨论】:

      • 我猜你没有测试这个? mat[4] 是一个明显的运行时错误。
      【解决方案5】:

      这与之前给出的答案相似,但有一些修改。正如其他答案中所述,记忆是另一种方法,但我不喜欢不会随着技术变化而扩展的代码(unsigned int 的大小因平台而异)所以序列中的最高值可以是达到的程度也可能会有所不同,在我看来记忆力很差。

      #include <iostream>
      
      using namespace std;
      
      void fibonacci(unsigned int count) {
         unsigned int x=0,y=1,z=0;
         while(count--!=0) {
            cout << x << endl;  // you can put x in an array or whatever
            z = x;
            x = y;
            y += z;
         }
      }
      
      int main() {
         fibonacci(48);// 48 values in the sequence is the maximum for a 32-bit unsigend int
         return 0;
      }
      

      此外,如果您使用&lt;limits&gt;,则可以编写一个编译时常量表达式,该表达式将为您提供序列中任何整数数据类型都可以达到的最大索引。

      【讨论】:

      • 虽然记忆可能对诗歌和乘法表很有帮助,但一种不重新计算函数值的机制似乎被称为记忆。
      【解决方案6】:
      public static int GetNthFibonacci(int n)
          {
              var previous = -1;
              var current = 1;
              int element = 0;
      
              while (1 <= n--)
              {
                  element = previous + current;
                  previous = current;
                  current = element;
              }
      
              return element;
          }
      

      【讨论】:

        【解决方案7】:
        using namespace std;
        
        void mult(LL A[ 3 ][ 3 ], LL B[ 3 ][ 3 ]) {
        
             int i,
        
                 j,
        
                 z;
        
             LL C[ 3 ][ 3 ];
        
             memset(C, 0, sizeof( C ));
        
             for(i = 1; i <= N; i++)
        
                 for(j = 1; j <= N; j++) {
        
                     for(z = 1; z <= N; z++)
        
                         C[ i ][ j ] = (C[ i ][ j ] + A[ i ][ z ] * B[ z ][ j ] % mod ) % mod;
                 }
        
             memcpy(A, C, sizeof(C));
        };
        
        void readAndsolve() {
        
            int i;
        
            LL k;
        
            ifstream I(FIN);
            ofstream O(FOUT);
        
            I>>k;
        
            LL A[3][3];
            LL B[3][3];
        
            A[1][1] = 1; A[1][2] = 0;
            A[2][1] = 0; A[2][2] = 1;
        
            B[1][1] = 0; B[1][2] = 1;
            B[2][1] = 1; B[2][2] = 1;
        
            for(i = 0; ((1<<i) <= k); i++) {
        
                  if( k & (1<<i) ) mult(A, B);
        
                  mult(B, B);
            }
        
            O<<A[2][1];
        }
        
        //1,1,2,3,5,8,13,21,33,...
        
        int main() {
        
            readAndsolve();
        
            return(0);
        }
        

        【讨论】:

          【解决方案8】:

          查看维基百科,有一个formula 给出了斐波那契数列中的数字,根本没有递归

          【讨论】:

          • 没有递归,也不能保证准确的结果。
          • 为什么没有确切的结果?这个公式实际上给出了确切的答案,因为你在计算时没有四舍五入。授予公式很复杂,但它的执行时间也与您要查找的数字无关。
          • @IVlad:这无关紧要。只要公式收敛,一旦收敛足够接近,舍入是安全的(显然,只要您注意有效数字)。
          • int main() { const double goldenRatio = (1.0 + sqrt(5.0)) / 2.0; for ( int i = 1; i
          • @swegi - 我不知道,但我的意思是在某些情况下你必须处理潜在的浮点错误。例如,他们不允许 Maple 参加编程比赛。我要说的是,公式并不总是最好的答案,因为它很容易产生错误的结果。如果您可以使用某些工具,那就太好了。从理论上讲这也很好,如果 OP 正在处理 32 位数字,那么它也可能对他有好处。同样,我的观点是它可能在这种情况下导致错误。
          【解决方案9】:

          在 C# 中:

                  static int fib(int n)
                  {
                      if (n < 2) return n;
                      if (n == 2) return 1;
                      int k = n / 2;
                      int a = fib(k + 1);
                      int b = fib(k);
                      if (n % 2 == 1)
                          return a * a + b * b;
                      else
                          return b * (2 * a - b);
                  }
          

          【讨论】:

            【解决方案10】:
            #include<stdio.h>
            main()
            {
             int a,b=2,c=5,d;
               printf("%d %d ");
            do
            {
               d=b+c;
                 b=c;
                c=d;
               rintf("%d ");
              }
            

            【讨论】:

              【解决方案11】:

              使用其中任何一个:递归的两个示例,一个使用 for 循环 O(n) 时间,一个使用黄金比例 O(1) 时间:

              private static long fibonacciWithLoop(int input) {
                  long prev = 0, curr = 1, next = 0;      
                  for(int i = 1; i < input; i++){
                      next = curr + prev;
                      prev = curr;
                      curr = next;
                  }
                  return curr;
              }
              
              public static long fibonacciGoldenRatio(int input) {
                  double termA = Math.pow(((1 + Math.sqrt(5))/2), input);
                  double termB = Math.pow(((1 - Math.sqrt(5))/2), input);
                  double factor = 1/Math.sqrt(5);
                  return Math.round(factor * (termA - termB));
              }
              
              public static long fibonacciRecursive(int input) {
                  if (input <= 1) return input;
                  return fibonacciRecursive(input - 1) + fibonacciRecursive(input - 2);
              }
              
              public static long fibonacciRecursiveImproved(int input) {
                  if (input == 0) return 0;
                  if (input == 1) return 1;
                  if (input == 2) return 1;
                  if (input >= 93) throw new RuntimeException("Input out of bounds");
                  // n is odd
                  if (input % 2 != 0) {
                      long a = fibonacciRecursiveImproved((input+1)/2);
                      long b = fibonacciRecursiveImproved((input-1)/2);
                      return a*a + b*b;
                  }
              
                  // n is even
                  long a = fibonacciRecursiveImproved(input/2 + 1);
                  long b = fibonacciRecursiveImproved(input/2 - 1);
                  return a*a - b*b;
              }
              

              【讨论】:

              • 黄金比例版本并不是真正的 O(1),不是吗?有 pow 方法。
              【解决方案12】:

              在函数式编程中有一种特殊的计算斐波那契的算法。该算法使用累积递归。累积递归用于最小化算法使用的堆栈大小。我认为这将帮助您减少时间。如果你愿意,你可以试试。

              int ackFib (int n, int m, int count){
                  if (count == 0)
                      return m;
                  else
                      return ackFib(n+m, n, count-1);
              }
              
              
              
              int fib(int n)
              {
               return ackFib (0, 1, n+1);
              }
              

              【讨论】:

                【解决方案13】:

                您可以通过matrix multiplictation 执行此操作,将矩阵提升到 n 次方,然后将其乘以向量。您可以在对数时间内将其提高到幂。

                我想你可以找到问题here。它是罗马尼亚语,但您可以使用谷歌翻译进行翻译。这正是您想要的,并且在那里列出了解决方案。

                【讨论】:

                • 我认为您正在回答一个不同的问题。这甚至与斐波那契数无关。
                • @A. Levy - 是的,它是相关的,您可以将某个矩阵提高到某个幂并在O(log n) 中获得斐波那契数。我同意答案非常模糊,并且链接非常无用,因为知道矩阵乘法并不能真正帮助您查看解决方案。也许他打算链接到斐波那契页面。无论哪种方式,答案都没有错,所以我个人认为没有必要投反对票。
                • 它与斐波那契数非常相关。例如,参见算法 5:ics.uci.edu/~eppstein/161/960109.html 每次被问到这个问题的变体时,这个答案经常出现在 stackoverflow 上(我是这样知道的,该链接只是谷歌搜索的第一次点击)
                • @A.Levy 在这里工作的例子stackoverflow.com/questions/1525521/…
                • 呵呵,你知道什么...我以前从未听说过。我撤回了我的反对票并添加了一个赞成票。谢谢你教我一些新的 Teodor!
                【解决方案14】:
                #include<stdio.h>
                
                 int g(int n,int x,int y)
                   {
                   return n==0 ? x : g(n-1,y,x+y);}
                
                 int f(int n)
                   {
                   return g(n,0,1);}
                
                 int main (void)
                   {  
                   int i;
                   for(i=1; i<=10 ; i++)
                     printf("%d\n",f(i)
                   return 0;
                   }
                

                【讨论】:

                  【解决方案15】:

                  您是否保证像您的示例一样,输入将按升序提供给您?如果是这样,你甚至不需要记忆;只需跟踪最后两个结果,开始生成序列,但如果 N 是输入中的下一个索引,则仅显示序列中的第 N 个数字。到达索引 0 时停止。

                  类似这样的:

                  int i = 0;
                  while ( true ) {
                      i++; //increment index
                      fib_at_i = generate_next_fib()
                      while ( next_input_index() == i ) {
                          println fib_at_i
                  }
                  

                  我将退出条件和实际生成序列留给你。

                  【讨论】:

                  • 即使它们没有排序,您也可以将它们加载到数组中,将指针列表排序到该数组中(不是数组,因此您不会破坏原始顺序),然​​后计算排序列表的斐波那契数,然后将它们映射回原始位置。如果您计划使用某种 bigint 方法支持较大的 n 值,这将比记忆更有效,因为您只需要保留 2 个 bigint,而不是可能有数千个。
                  【解决方案16】:

                  使用golden-ratio

                  【讨论】:

                  • 如果他需要确切的结果,这是一个非常糟糕的主意,似乎就是这样。
                  • 不一定,请参阅 David Brunelle 帖子中的讨论。
                  • @IVlad - 是的,这将取决于浮点表示,并且在我的 32 位机器上使用 Ruby 中的常规浮点数,它开始在第 70 个数字附近发散。精度必须更高,并且可能需要手写表示更大的数字。
                  • 就像@swegi 和@Brian 所说的那样,数学语言将提供所需的精度并且对更大的数字有效,这将是一个有趣的测试。
                  • @Anurag - 是的,它开始在 C++ 中的第 72 个数字处为我提供不正确的结果,使用双打。无论如何,它给出的结果不正确的那些数字不适合 32 位整数,我猜 OP 会使用这些数字。所以我对“非常糟糕的主意”有点过分了,我道歉。尽管如此,在这样的公式中,错误绝对是需要注意的。
                  【解决方案17】:

                  首先,您可以使用memoization 或相同算法的迭代实现。

                  考虑您的算法进行的递归调用次数:

                  fibonacci(n) 调用 fibonacci(n-1) 和 fibonacci(n-2)
                  fibonacci(n-1) 调用 fibonacci(n-2)fibonacci(n-3)
                  fibonacci(n-2) 调用 fibonacci(n-3) 和 fibonacci(n-4)

                  注意到一个模式了吗?您计算相同函数的次数比需要的次数多得多。

                  迭代实现会使用数组:

                  int fibonacci(int n) {
                      int arr[maxSize + 1]; 
                      arr[1] = arr[2] = 1; // ideally you would use 0-indexing, but I'm just trying to get a point across
                      for ( int i = 3; i <= n; ++i )
                          arr[i] = arr[i - 1] + arr[i - 2]; 
                  
                      return arr[n];
                  }
                  

                  这已经比你的方法快得多了。您可以按照相同的原则更快地完成此操作,只需构建一次数组直到最大值n,然后通过打印数组的元素在一次操作中打印正确的数字。这样您就不会为每个查询调用该函数。

                  如果您负担不起初始预计算时间(但这通常仅在您被要求对结果取模时才会发生,否则他们可能不希望您实现大数算术并且预计算是最好的解决方案) ,阅读fibonacci wiki page了解其他方法。专注于矩阵方法,这是在比赛中很好了解的方法。

                  【讨论】:

                    【解决方案18】:

                    使用memoization。也就是说,您缓存答案以避免不必要的递归调用。

                    这是一个代码示例:

                    #include <stdio.h>
                    
                    int memo[10000]; // adjust to however big you need, but the result must fit in an int
                                     // and keep in mind that fibonacci values grow rapidly :)
                    
                    int fibonacci(int n) {
                      if (memo[n] != -1)
                        return memo[n];
                    
                      if (n==1 || n==2)
                        return 1;
                      else
                        return memo[n] = fibonacci(n-1) +fibonacci(n-2);
                    }
                    int main() {
                      for(int i = 0; i < 10000; ++i)
                        memo[i] = -1;
                      fibonacci(50);
                    }
                    

                    【讨论】:

                    • 我不认为像这样计算fibonacci(500) 会起作用:)。
                    • 是的,它会溢出。我把它改成了 50。谢谢你的评论 :)。
                    • 现在把无用的代码去掉,换成static const int memo[50] = { 0, 1, 1, 2, 3, 5, ... };
                    • @R- memo 数组的重点是为了memoization,也就是说,我们使用该数组来避免不必要的递归调用。用答案预先初始化数组会破坏目的,我们可以完全消除斐波那契函数。也许你需要阅读我发布的记忆链接。
                    【解决方案19】:

                    可以减少 if 语句的开销:Calculating Fibonacci Numbers Recursively in C

                    【讨论】:

                      【解决方案20】:

                      构建一个数组 Answer[100],在其中缓存 fibonacci(n) 的结果。 检查你的斐波那契代码,看看你是否已经预先计算了答案,并且 使用该结果。结果会让你大吃一惊。

                      【讨论】:

                      • 不需要保留所有答案,只保留最后两个。
                      • @Ben313:我认为你是对的。它不符合记忆的精神,您只是存储以前计算的结果,希望它们能被重用。使用您的方案,您实际上必须考虑:-} 通过将它们全部缓存,您不必费力思考,它就可以工作。完整的缓存会更快地产生 set 答案,尽管我同意对于 Fib 可能无关紧要。无论如何都不错。
                      【解决方案21】:

                      您的算法是递归的,并且大约具有 O(2^N) 复杂度。

                      这个问题之前已经在stackoverflow上讨论过: Computational complexity of Fibonacci Sequence

                      在该特定讨论中还发布了一个更快的实现。

                      【讨论】:

                        【解决方案22】:

                        你能发布 input.txt 文件和时间限制吗? 顺便说一句:这个任务是众所周知的。你读过以下http://www.ics.uci.edu/~eppstein/161/960109.html 吗?

                        【讨论】:

                          【解决方案23】:

                          你可能应该研究一下记忆​​。

                          http://en.wikipedia.org/wiki/Memoization

                          那里有一个解释和一个fib示例

                          【讨论】:

                          • @R - 但最好让 OP 学习这种技术,因为它非常有价值。所以我不会说它根本无关紧要,它在编程竞赛中尤其重要。在更简单的问题上学习技术是很好的,比如斐波那契,然后它可以应用于更困难的问题。此外,可以使用 64 位整数,然后可以存储更大的答案。或者在 C# 4.0 或 JAVA 中,还有 BigInteger,因此甚至可以存储非常大的数字。
                          猜你喜欢
                          • 2021-08-04
                          • 1970-01-01
                          • 2011-12-18
                          • 2011-04-26
                          • 2015-04-25
                          • 1970-01-01
                          • 2017-11-13
                          • 2013-02-24
                          • 1970-01-01
                          相关资源
                          最近更新 更多