Java 递归与迭代(斐波那契序列)

Java 递归与迭代(斐波那契序列),java,recursion,iteration,Java,Recursion,Iteration,我有两种不同的方法,一种是用迭代法计算第n个元素的斐波那契序列,另一种是用递归法做同样的事情 程序示例如下所示: import java.util.Scanner; public class recursionVsIteration { public static void main(String[] args) { Scanner sc = new Scanner(System.in); //nth element input

我有两种不同的方法,一种是用迭代法计算第n个元素的斐波那契序列,另一种是用递归法做同样的事情


程序示例如下所示:

import java.util.Scanner;

public class recursionVsIteration {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);

        //nth element input
        System.out.print("Enter the last element of Fibonacci sequence: ");
        int n = sc.nextInt();

        //Print out iteration method
        System.out.println("Fibonacci iteration:");
        long start = System.currentTimeMillis();
        System.out.printf("Fibonacci sequence(element at index %d) = %d \n", n, fibIteration(n));
        System.out.printf("Time: %d ms\n", System.currentTimeMillis() - start);

        //Print out recursive method
        System.out.println("Fibonacci recursion:");
        start = System.currentTimeMillis();
        System.out.printf("Fibonacci sequence(element at index %d) = %d \n", n, fibRecursion(n));
        System.out.printf("Time: %d ms\n", System.currentTimeMillis() - start);
    }

    //Iteration method
    static int fibIteration(int n) {
        int x = 0, y = 1, z = 1;
        for (int i = 0; i < n; i++) {
            x = y;
            y = z;
            z = x + y;
        }
        return x;
    }

    //Recursive method
    static int fibRecursion(int  n) {
        if ((n == 1) || (n == 0)) {
            return n;
        }
        return fibRecursion(n - 1) + fibRecursion(n - 2);
    }
}

示例2(n=20)


示例#3(n=30)


我真正想知道的是为什么迭代突然变得更快,递归变得更慢。我很抱歉,如果我错过了这个问题的一些明显的答案,但我还是一个新的编程,我真的不明白背后发生了什么,我想知道。请提供一个很好的解释或者给我指出正确的方向,这样我就能自己找到答案。此外,如果这不是测试哪种方法更快的好方法,请让我知道并建议我不同的方法

提前感谢

对递归和迭代进行了比较,并介绍了它们在生成斐波那契数方面的应用

如文中所述

性能差的原因是在每个递归调用的病态级别中寄存器的大量推送pop

这基本上说明递归方法有更多的开销


另外,请看一下

在执行斐波那契算法的递归实现时,您通过反复重新计算相同的值来添加冗余调用

fib(5) = fib(4) + fib(3)
fib(4) = fib(3) + fib(2)
fib(3) = fib(2) + fib(1)
请注意,
fib(2)
将对
fib(4)
fib(3)
进行冗余计算。 然而,这可以通过一种称为的技术来克服,该技术通过存储已经计算过一次的值来提高递归斐波那契的效率。对于已知值的
fib(x)
的进一步调用可以被简单的查找所取代,从而消除了进一步递归调用的需要

这是迭代法和递归法之间的主要区别,如果您感兴趣,还有其他更多的计算斐波那契数的方法。

为了简洁起见,让F(x)为递归斐波那契数

F(10) = F(9)                      + F(8)
F(10) = F(8)        + F(7)        + F(7) + F(6)
F(10) = F(7) + F(6) + F(6) + F(5) + 4 more calls.
....
所以你要打F(8)两次, F(7)3次,F(6)5次,F(5)7次。。等等


因此,输入越大,树就越大。

使用递归的方法,时间复杂度是
O(fib(n))
,这是非常昂贵的。迭代方法是
O(n)
这没有显示,因为a)您的测试非常短,代码甚至不会被编译b)您使用了非常小的数字


这两个示例运行得越快。一旦循环或方法被调用10000次,就应该将其编译为本机代码

如果有人对数组的迭代函数感兴趣:

public static void fibonacci(int y)
{
    int[] a = new int[y+1];
    a[0] = 0;
    a[1] = 1;
    System.out.println("Step 0: 0");
    System.out.println("Step 1: 1");
    for(int i=2; i<=y; i++){
        a[i] = a[i-1] + a[i-2];
        System.out.println("Step "+i+": "+a[i]);
    }
    System.out.println("Array size --> "+a.length);
}
publicstaticvoidfibonacci(inty)
{
int[]a=新的int[y+1];
a[0]=0;
a[1]=1;
System.out.println(“步骤0:0”);
System.out.println(“步骤1:1”);

对于(inti=2;i为什么递归速度较慢?

当您再次调用函数本身(作为递归)时,编译器将分配新的激活记录(只需将其视为普通堆栈)对于该新函数。该堆栈用于保存状态、变量和地址。编译器为每个函数创建一个堆栈,此创建过程将继续,直到达到基本情况。因此,当数据大小变大时,编译器需要较大的堆栈段来计算整个过程。计算和管理在这个过程中,这些记录也会被计算在内

此外,在递归中,堆栈段在运行时被提升。编译器不知道编译时将占用多少内存


这就是为什么如果你不正确处理你的基本情况,你会得到StackOverflow异常:)。

我更喜欢使用黄金数的数学解。享受吧

private static final double GOLDEN_NUMBER = 1.618d;

public long fibonacci(int n) {
    double sqrt = Math.sqrt(5);

    double result = Math.pow(GOLDEN_NUMBER, n);

    result = result - Math.pow(1d - GOLDEN_NUMBER, n);

    result = Math.round(result / sqrt);

    return Double.valueOf(result).longValue();
}

每当你在寻找完成一个特定算法所需的时间时,最好总是考虑时间复杂度

用O(某物)来评估纸上的时间复杂度

比较上述两种方法,迭代方法的时间复杂度为O(n),而递归方法的时间复杂度为O(2^n)

让我们试着找出
fib(4)

迭代方法,循环计算4次,因此其时间复杂度为
O(n)

递归方法

                               fib(4)

             fib(3)              +               fib(2)

      fib(2)   +    fib(1)           fib(1)     +       fib(0)

fib(1)  +  fib(0)
因此fib()被调用了9次,当n的值很大时,它比2^n稍低,甚至很小(请记住,
BigOh
(O)负责
上限


因此,我们可以说迭代方法是在多项式时间内进行计算的,而递归方法是在指数时间内进行计算的。您使用的递归方法效率不高。我建议您使用尾部递归。与此相反,尾部递归只在在任何时间点进行e堆栈

public static int tailFib(int n) {
    if (n <= 1) {
        return n;
    }
    return tailFib(0, 1, n);
}

private static int tailFib(int a, int b, int count) {
    if(count <= 0) {
        return a;
    }
    return tailFib(b, a+b, count-1);
}

public static void main(String[] args)  throws Exception{
    for (int i = 0; i <10; i++){
        System.out.println(tailFib(i));
    }
}
public static int tailFib(int n){

如果(n我有一个递归解决方案,您可以将计算值存储在何处,以避免进一步不必要的计算。下面提供了代码

public static int fibonacci(int n) {

        if(n <=  0) return 0;
        if(n == 1) return 1;

        int[] arr = new int[n+1];

        // this is faster than using Array
        // List<Integer> lis = new ArrayList<>(Collections.nCopies(n+1, 0));

        arr[0] = 0;
        arr[1] = 1; 

        return fiboHelper(n, arr);
    }

    public static int fiboHelper(int n, int[] arr){

        if(n <= 0) {
            return arr[0];
        }

        else if(n == 1) {
            return arr[1];
        }

        else {

            if( arr[n-1] != 0 && (arr[n-2] != 0 || (arr[n-2] == 0 && n-2 == 0))){    
                return arr[n] = arr[n-1] + arr[n-2]; 
            }

            else if (arr[n-1] == 0 && arr[n-2] != 0 ){
                return arr[n] = fiboHelper(n-1, arr) + arr[n-2]; 
            }

            else {
                return  arr[n] = fiboHelper(n-2, arr) + fiboHelper(n-1, arr );
            } 

        }             
    }
公共静态int fibonacci(int n){

if(n)递归调用函数会增加开销。这两种方法不完全相同(甚至不包括图中的递归/迭代)第一个问题:您的基准测试方法存在严重缺陷。您确实没有做足够的工作来准确测量差异。您应该使用
System.nanoTime
,并重复调用多次,以便测量有用的工作量。接下来,看看每个调用的复杂性……计算出eac中完成了多少工作提示:试着在纸上走一遍,如果你把fibRecursion(8)和FibreIteration(8)叫做什么。最快的方法是
                               fib(4)

             fib(3)              +               fib(2)

      fib(2)   +    fib(1)           fib(1)     +       fib(0)

fib(1)  +  fib(0)
public static int tailFib(int n) {
    if (n <= 1) {
        return n;
    }
    return tailFib(0, 1, n);
}

private static int tailFib(int a, int b, int count) {
    if(count <= 0) {
        return a;
    }
    return tailFib(b, a+b, count-1);
}

public static void main(String[] args)  throws Exception{
    for (int i = 0; i <10; i++){
        System.out.println(tailFib(i));
    }
}
public static int fibonacci(int n) {

        if(n <=  0) return 0;
        if(n == 1) return 1;

        int[] arr = new int[n+1];

        // this is faster than using Array
        // List<Integer> lis = new ArrayList<>(Collections.nCopies(n+1, 0));

        arr[0] = 0;
        arr[1] = 1; 

        return fiboHelper(n, arr);
    }

    public static int fiboHelper(int n, int[] arr){

        if(n <= 0) {
            return arr[0];
        }

        else if(n == 1) {
            return arr[1];
        }

        else {

            if( arr[n-1] != 0 && (arr[n-2] != 0 || (arr[n-2] == 0 && n-2 == 0))){    
                return arr[n] = arr[n-1] + arr[n-2]; 
            }

            else if (arr[n-1] == 0 && arr[n-2] != 0 ){
                return arr[n] = fiboHelper(n-1, arr) + arr[n-2]; 
            }

            else {
                return  arr[n] = fiboHelper(n-2, arr) + fiboHelper(n-1, arr );
            } 

        }             
    }