递归与迭代(Fibonacci序列)

时间:2014-02-11 19:02:30

标签: java recursion iteration

我有两种不同的方法,一种是使用迭代计算Fibonacci序列到 nth 元素,另一种是使用递归方法做同样的事情。


程序示例如下所示:

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);
    }
}

我试图找出哪种方法更快。我得出的结论是,对于较小的数字,递归更快,但随着 nth 元素的值增加,递归变得更慢并且迭代变得更快。以下是三种不同 n 的三种不同结果:


示例#1(n = 10)

Enter the last element of Fibonacci sequence: 10
Fibonacci iteration:
Fibonacci sequence(element at index 10) = 55 
Time: 5 ms
Fibonacci recursion:
Fibonacci sequence(element at index 10) = 55 
Time: 0 ms

示例#2(n = 20)

Enter the last element of Fibonacci sequence: 20
Fibonacci iteration:
Fibonacci sequence(element at index 20) = 6765 
Time: 4 ms
Fibonacci recursion:
Fibonacci sequence(element at index 20) = 6765 
Time: 2 ms

示例#3(n = 30)

Enter the last element of Fibonacci sequence: 30
Fibonacci iteration:
Fibonacci sequence(element at index 30) = 832040
Time: 4 ms
Fibonacci recursion:
Fibonacci sequence(element at index 30) = 832040
Time: 15 ms

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

提前致谢!

10 个答案:

答案 0 :(得分:14)

对于简洁性,设F(x)为递归Fibonacci

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次..等等

因此,对于较大的输入,树会越来越大。

答案 1 :(得分:6)

This article对递归和迭代进行了比较,并涵盖了它们在生成斐波那契数字时的应用。

如文章所述,

  

性能不佳的原因是在每次递归调用的不良级别中寄存器的重击。

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

另外,请查看Memoization

答案 2 :(得分:6)

在执行fibonacci算法的递归实现时,您通过一遍又一遍地重新计算相同的值来添加冗余调用。

fib(5) = fib(4) + fib(3)
fib(4) = fib(3) + fib(2)
fib(3) = fib(2) + fib(1)

请注意,fib(2)fib(4)都会fib(3)进行冗余计算。 然而,这可以通过一种称为Memoization的技术来克服,通过存储您计算过一次的值来提高递归斐波纳契的效率。 fib(x)对已知值的进一步调用可以用简单的查找替换,从而无需进一步的递归调用。

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

答案 3 :(得分:4)

为什么递归较慢?

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

此外,在递归中,堆栈段在运行时引发。编译器不知道在编译期间将占用多少内存

这就是为什么如果你没有正确处理基本情况,你会得到 StackOverflow 例外:)。

答案 4 :(得分:2)

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

运行它们越多,两个示例都会变得越快。一旦循环或方法被调用10,000次,它应该被编译为本机代码。

答案 5 :(得分:2)

每当您在寻找完成特定算法所需的时间时,最好总是追求时间复杂性。

根据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)负责upper bound

结果我们可以说迭代方法是在polynomial time中进行评估,而递归方法是在exponential time进行评估

答案 6 :(得分:1)

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

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);
}

此解决方案因输入值0而崩溃。

原因:数组a将被初始化0+1=1,但连续分配a[1]将导致索引超出范围异常。

0上添加一个返回y=0的if语句或者按y+2初始化数组,这将浪费1 int但仍然是恒定的空间而不是更改大O

答案 7 :(得分:1)

我更喜欢使用黄金数字的数学解决方案。享受

    @Override
public View getView(int position, View convertView, ViewGroup parent) {

    if (inflater == null)
        inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    if (convertView == null)
        convertView = inflater.inflate(R.layout.list_row, null);

    TextView due_time = (TextView) convertView.findViewById(R.id.due_time);

    // getting order data for the row
    Order o = orderItems.get(position);

    // Due Time
    due_time.setText(o.getDueTime());

    return convertView;
}

答案 8 :(得分:1)

您使用的递归方法效率不高。我建议你使用尾递归。与您的方法相反,尾递归在任何时间点都只在堆栈中保留一个函数调用。

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));
    }
}

答案 9 :(得分:1)

我有一个递归解决方案,您可以在其中存储计算值,以避免进一步不必要的计算。代码如下:

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 );
            } 

        }             
    }