【问题标题】:Java Program Fibonacci SequenceJava 程序斐波那契数列
【发布时间】:2010-12-20 11:25:57
【问题描述】:

我正在编写一个“简单”程序来确定斐波那契数列中的第 N 个数字。例如:序列中的第 7 个数字是:13。我已经完成了程序,它可以工作,但是从第 40 个数字开始它开始延迟,并且花费的时间越来越长。我的程序必须排到该系列的第 100 位。

我怎样才能解决这个问题,所以它不会花这么长时间?这是一个非常基本的程序,所以我不知道所有花哨的语法代码。我的公式是:

if n =1 || n = 0
   return n;

else 
    return F(n-1) + F(n-2);

在第 40 届任期结束之前,这非常有效。我还需要添加什么其他语句才能让它更快地处理更大的数字??

【问题讨论】:

  • 如果这是作业,请用“作业”标签标记它。
  • 此调用的次数与每个号码使用指数级更多调用的结果大致相同。

标签: java fibonacci


【解决方案1】:

问题在于,由于您使用的是简单递归,因此您多次重新评估 F(n),因此您的执行时间是指数级的。

有两种简单的方法可以解决这个问题:

1) 在第一次评估 F(n) 时缓存它们的值。在计算 F(n) 之前先检查缓存,看看你是否已经为这个 n 计算过。

2) 使用迭代方法:计算 F(1)、F(2)、F(3) 等...直到达到所需的数字。

【讨论】:

    【解决方案2】:

    问题在于您的算法虽然在数学上很纯(而且很好),但并不是很好。
    对于它要计算的每个数字,它必须计算两个较低的数字,而后者又必须计算两个较低的数字,依此类推。您当前的算法的 Big O notation 复杂度约为 O(1.6n),因此对于非常大的数字(100例如)需要很长时间。

    这本书,Structure and Interpretation of Computer programs has a nice diagram:展示了使用算法生成 fib 5 时会发生什么

    (来源:mit.edu)

    最简单的做法是存储F-1和F-2,这样就不用每次都从头开始计算了。换句话说,与其使用递归,不如使用循环。比意味着算法的复杂度从 O(1.6n) 到 O(n)。

    【讨论】:

      【解决方案3】:

      有多种解决方案。最直接的方法是使用memoization。还有Binet's formula,它将在恒定时间内为您提供第 n 个斐波那契数。

      对于记忆,您将 F[a_i] 的结果存储在某种地图或列表中。例如,在朴素递归中,您计算​​ F[4] 数十万次。通过在找到所有这些结果时存储它们,递归就不再像一棵树一样进行,而是看起来像直接的迭代解决方案。

      如果这不是家庭作业,请使用比内公式。这是最快的方法。

      【讨论】:

      • 比奈的公式只会给你一个恒定时间的近似值。如果您需要使用此方法的准确答案,则需要任意精度的浮点运算,这在恒定时间内是不可能的。
      • 我以为比内特给了你 e,还是只有当 n=1 时才这样?
      • 你说的是硬件问题,朋友,不是软件问题:)。误差足够小,您只需四舍五入即可获得正确的整数值。我不知道这在什么时候发生了故障,但是一个简单的程序测试(使用双精度)可以证实这一点。
      • 绝对可以达到 F[100]。在更高的数字之前,您可能不会注意到错误。
      • 另外,这是速度与准确性的经典示例。 Binet 的公式对于任何 F[n] 水平的纯近似都将是超快的,但较慢的方法保证了准确性。无论如何,任何使用斐波那契数的程序可能应该只使用保证精度算法预先计算所有值。您最终需要 400 个字节来预先计算足够的值来解决 OP 的条件。
      【解决方案4】:

      试试这个例子,它会在合理的时间范围内计算出百万分之一的斐波那契数,而不会损失任何精度。

      import java.math.BigInteger;
      
      /*
      250000th fib # is: 36356117010939561826426 .... 10243516470957309231046875
      Time to compute: 3.5 seconds.
      1000000th fib # is: 1953282128707757731632 .... 93411568996526838242546875
      Time to compute: 58.1 seconds.
      */
      public class Fib {
          public static void main(String... args) {
              int place = args.length > 0 ? Integer.parseInt(args[0]) : 1000 * 1000;
              long start = System.nanoTime();
              BigInteger fibNumber = fib(place);
              long time = System.nanoTime() - start;
      
              System.out.println(place + "th fib # is: " + fibNumber);
              System.out.printf("Time to compute: %5.1f seconds.%n", time / 1.0e9);
          }
      
          private static BigInteger fib(int place) {
              BigInteger a = new BigInteger("0");
              BigInteger b = new BigInteger("1");
              while (place-- > 1) {
                  BigInteger t = b;
                  b = a.add(b);
                  a = t;
              }
              return b;
          }
      }
      

      【讨论】:

        【解决方案5】:

        创建一个包含 100 个值的数组,然后在计算 Fib(n) 的值时,将其存储在数组中并使用该数组获取 Fib(n-1) 和 Fib(n-2) 的值。

        如果您在调用 Fib(100) 时没有存储任何先前计算的值,您将使您的 java 运行时爆炸。

        伪代码:

        array[0] = 0;
        array[1] = 1;
        for 2:100
        array[n] = array[n-1] + array[n-2];
        

        【讨论】:

          【解决方案6】:

          问题不在于 JAVA,而在于您实现斐波那契算法的方式。 您多次计算相同的值,这会减慢您的程序。

          试试这样的:Fibonacci with memoization

          【讨论】:

            【解决方案7】:
                                       F(n)
                                        /    \
                                    F(n-1)   F(n-2)
                                    /   \     /      \
                                F(n-2) F(n-3) F(n-3)  F(n-4)
                               /    \
                             F(n-3) F(n-4)
            

            请注意,许多计算是重复的! 需要注意的重要一点是该算法是指数的,因为它不存储先前计算的数字的结果。例如 F(n-3) 被调用了 3 次。

            更好的解决方案是下面写的迭代代码

            function fib2(n) {
                if n = 0 
                   return 0
                create an array f[0.... n]
                f[0] = 0, f[1] = 1
                for i = 2...n:
                   f[i] = f[i - 1] + f[i - 2]
                return f[n]    
            }
            

            更多细节请参考 dasgupta 第 0.2 章的算法

            【讨论】:

              【解决方案8】:

              我使用 Java 8 Stream 的解决方案:

              public class Main {
                  public static void main(String[] args) {
                      int n = 10;
                      Fibonacci fibonacci = new Fibonacci();
                      LongStream.generate(fibonacci::next)
                              .skip(n)
                              .findFirst()
                              .ifPresent(System.out::println);
                  }
              }
              
              public class Fibonacci {
                  private long next = 1;
                  private long current = 1;
              
                  public long next() {
                      long result = current;
                      long previous = current;
                      current = next;
                      next = current + previous;
                      return result;
                  }
              }
              

              【讨论】:

                【解决方案9】:

                如果你使用简单的方法,你最终会得到大量相同的计算,即要计算 fib(n),你必须计算 fib(n-1) 和 fib(n-2)。然后要计算 fib(n-1),您必须计算 fib(n-2) 和 fib(n-3) 等。更好的方法是逆向计算。您从 fib(0)、fib(1)、fib(2) 开始计算并将值存储在表中。然后使用存储在表(数组)中的值来计算后续值。这也称为记忆。试试这个,你应该能够计算出大的 fib 数。

                【讨论】:

                  【解决方案10】:

                  这是 Python 中的代码,可以很容易地转换为 C/Java。第一个是递归的,第二个是迭代的解决方案。

                  def fibo(n, i=1, s=1, s_1=0):
                      if n <= i: return s
                      else: return fibo(n, i+1, s+s_1, s)
                  
                  
                  def fibo_iter_code(n):
                      s, s_1 = 1, 0
                      for i in range(n-1):
                         temp = s
                         s, s_1 = s+s_1, temp
                         print(s)
                  

                  【讨论】:

                    【解决方案11】:

                    太慢了……

                    更好: (JavaScript 示例)

                    function fibonacci(n) {
                        var a = 0, b = 1;
                    
                        for (var i = 0; i < n; i++) {
                            a += b;
                            b = a - b;
                        }
                        return a;
                    }
                    

                    【讨论】:

                      【解决方案12】:
                      import java.util.*;
                      public class FibonacciNumber
                      {
                      
                        public static void main(String[] args)
                        {
                          int high = 1, low = 1;
                          int num;
                          Scanner in = new Scanner(System.in);
                          try
                          {
                            System.out.print("Enter Number : " );
                            num = in.nextInt(); 
                            System.out.println( low);
                            while(high < num && num < 2000000000)
                            {
                              System.out.println(high);
                              high = low + high;
                              low = high - low;
                            }
                           } catch (InputMismatchException e) {
                             System.out.print("Limit Exceeded");
                           }
                         }
                      }
                      
                      /* Ouput : 
                      Enter Number : 1999999999
                      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
                      -1323752223
                      512559680
                      -811192543
                      -298632863
                      -1109825406
                      -1408458269
                       1776683621
                       368225352   */
                      

                      【讨论】:

                      • 如果你想用 0 开始斐波那契数,请将低值设置为零。在这个程序中,斐波那契数从 1 开始......
                      【解决方案13】:

                      朴素的实现是自然而优雅的,但在执行过程中递归调用会创建二叉树。除了已经提到的记忆化、先前 F(n) 结果的兑现和避免不必要的树遍历之外,您还可以进行尾调用优化,已经提到了迭代或矩阵乘法。例如,Java 8 记忆:

                      private static final Map<Long, Long> memo = new HashMap<>();
                      static {
                        memo.put(0L, 0L);
                        memo.put(1L, 1L);
                      }
                      public static void main(String[] args) {
                        System.out.println(fibonacci(0));
                        System.out.println(fibonacci(43));
                        System.out.println(fibonacci(92));
                      }
                      public static long fibonacci(long n) {
                        return memo.computeIfAbsent(n, m -> fibonacci(m - 1) + fibonacci(m - 2));
                      }
                      

                      或者也许是尾调用优化版本:

                      interface FewArgs<T, U, V, R> {
                        public R apply(T t, U u, V v);
                      }
                      
                      static FewArgs<Long, Long, Long, Long> tailRecursive;
                      
                      static {
                        tailRecursive = (a, b, n) -> {
                          if (n > 0)
                            return tailRecursive.apply(b, a + b, n - 1);
                          return a;
                        };
                      }
                      

                      你用 a = 0, b = 1 来调用它,n 是第 n 个斐波那契数,但必须小于 93。 计算斐波那契数的更有效方法是矩阵平方,您可以在我的博客上找到示例,以及 Binet 公式

                      【讨论】:

                        【解决方案14】:

                        您可以使用缓存技术。由于 f(n)= f(n-1)+f(n-2) ,当您计算 f(n-1) 时,您将再计算一次 f(n-2)。因此,只需将它们视为两个增量数字,如下所示:

                        public int fib(int ithNumber) {
                            int prev = 0;
                            int current = 1;
                            int newValue;
                            for (int i=1; i<ithNumber; i++) {
                                newValue = current + prev;
                                prev = current;
                                current = newValue;
                            }
                            return current;
                        }
                        

                        【讨论】:

                          【解决方案15】:

                          用三元运算符的多个语句看起来更好。

                          static int fib(int n) {
                                  return  n > 5 ? fib(n-2) + fib(n-1)
                                                : n < 2 || n == 5 ? n
                                                                  : n - 1;
                          }
                          

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2015-08-30
                            • 1970-01-01
                            • 1970-01-01
                            • 2015-06-05
                            • 2012-02-16
                            • 1970-01-01
                            相关资源
                            最近更新 更多