java中的fibonacci函数的尾调用优化

时间:2011-03-28 00:02:03

标签: java optimization recursion fibonacci tail

我正在研究Tail调用递归,并且遇到了一些提到的文档。 Sun Java没有实现尾调用优化。 我写了以下代码来以3种不同的方式计算斐波纳契数: 1.迭代 2.头递归 3.尾递归

public class Fibonacci {
    public static void main(String[] args) throws InterruptedException {
        int n = Integer.parseInt(args[0]);
        System.out.println("\n Value of n : " + n);
        System.out.println("\n Using Iteration : ");
        long l1 = System.nanoTime();
        fibonacciIterative(n);
        long l2 = System.nanoTime();
        System.out.println("iterative time = " + (l2 - l1));
        System.out.println(fibonacciIterative(n));

        System.out.println("\n Using Tail recursion : ");
        long l3 = System.nanoTime();
        fibonacciTail(n);
        long l4 = System.nanoTime();
        System.out.println("Tail recursive time = " + (l4 - l3));
        System.out.println(fibonacciTail(n));

        System.out.println("\n Using Recursion : ");
        long l5 = System.nanoTime();
        fibonacciRecursive(n);
        long l6 = System.nanoTime();
        System.out.println("Head recursive time = " + (l6 - l5));
    }

    private static long fibonacciRecursive(int num) {
        if (num == 0) {
            return 0L;
        }
        if (num == 1) {
            return 1L;
        }
        return fibonacciRecursive(num - 1) + fibonacciRecursive(num - 2);
    }

    private static long fibonacciIterative(int n) throws InterruptedException {
        long[] arr = new long[n + 1];
        arr[0] = 0;
        arr[1] = 1;
        for (int i = 2; i <= n; i++) {
            // Thread.sleep(1);
            arr[i] = arr[i - 1] + arr[i - 2];
        }
        return arr[n];
    }

    private static long fibonacciTail(int n) {
        if (n == 0)
            return 0;
        return fibHelper(n, 1, 0, 1);
    }

    private static long fibHelper(int n, int m, long fibM_minus_one, long fibM) {
        if (n == m)
            return fibM;
        return fibHelper(n, m + 1, fibM, fibM_minus_one + fibM);
    }
}

在运行此程序时,我得出了一些结果:

  1. 头部递归方法未完成n> 50。程序看起来像被绞死了。任何想法,为什么会发生这种情况?
  2. 与Head递归相比,尾递归方法花费的时间要少得多。 有时比迭代方法花费更少的时间。这是否意味着java在内部进行了一些Tail调用优化? 如果确实如此,为什么我这样做会给出n>的StackOverflowError; 5000?
  3. 系统规格:

    Intel核心5处理器,

    Windows XP,

    32位Java 1.6

    JVM的默认堆栈大小。

4 个答案:

答案 0 :(得分:12)

  

这是否意味着java在内部进行了一些Tail调用优化?

不,它没有。 HotSpot JIT编译器不实现尾调用优化。

您正在观察的结果是您在Java基准测试中看到的异常情况的典型结果,该异常情况未考虑JVM预热。例如,调用方法的“前几”次,它将由解释器执行。然后JIT编译器将编译该方法......它将变得更快。

要获得有意义的结果,请在整个批次周围循环并运行多次,直到时间稳定为止。然后丢弃早期迭代的结果。

  

...为什么我这样做会在n&gt;处给出StackOverflowError 5000?

这只是证据表明没有任何尾调优化发生。

答案 1 :(得分:3)

对于第一个问题,2 ^ 50(或接近的东西)是什么?递归Fib函数中的每个数字N都调用它两次(之前的2)。这些中的每一个都调用了2个先前的迭代等等。所以它增长到递归的2 ^(N-k)(k可能是2或3)。

第二个问题是因为第二个问题是直接N递归。它不是双头(N-1),(N-2),而是从M = 1,M = 2 ...... M = N构建。每一步,N-1值都保留用于添加。由于它是O(N)操作,它与迭代方法相当,唯一的区别是JIT编译器如何优化它。递归的问题在于,它需要为堆叠到帧上的每个级别占用大量内存 - 在某些限制内耗尽内存或堆栈空间。 它通常应该比迭代方法慢。

答案 2 :(得分:2)

您可以使用Memoization来避免头部递归。

我已经测试了以下代码,当N <= 40时,这种方法很糟糕,因为Map需要权衡。

private static final Map<Integer,Long> map = new HashMap<Integer,Long>();

private static long fibonacciRecursiveMemoization(int num) {
    if (num == 0) {
        return 0L;
    }
    if (num == 1) {
        return 1L;
    }

    int num1 = num - 1;
    int num2 = num - 2;

    long numResult1 = 0;
    long numResult2 = 0;

    if(map.containsKey(num1)){
        numResult1 = map.get(num1);
    }else{
        numResult1 = fibonacciRecursiveMemoization(num1);
        map.put(num1, numResult1);
    }

    if(map.containsKey(num2)){
        numResult2 = map.get(num2);
    }else{
        numResult2 = fibonacciRecursiveMemoization(num2);
        map.put(num2, numResult2);
    }

    return numResult1 + numResult2;
}

当n:44的值

使用迭代: 迭代时间= 6984

使用Tail递归: 尾递归时间= 8940

使用Memoization递归: 记忆递归时间= 1799949

使用递归: 头递归时间= 12697568825

答案 3 :(得分:1)

关于第1点:在没有记忆的情况下递归计算Fibonacci数导致n中的指数运行时间。这适用于任何不自动记忆功能结果的编程语言(例如大多数主流非功能语言,例如Java,C#,C ++,......)。原因是一遍又一遍地调用相同的函数 - 例如f(8)会致电f(7)f(6); f(7)会调用f(6)f(5),以便f(6)被调用两次。此效果传播并导致函数调用数量呈指数增长。这里是一个可视化函数的可视化:

f(8)
 f(7)
  f(6)
   f(5)
    f(4)
     ...
    f(3)
     ...
   f(4)
    ...
  f(5)
   f(4)
    ...
   f(3)
    ...
 f(6)
  f(5)
   ...
  f(4)
   ...