尾遞歸優化小記

前言

通常地,對於java語言而言,普通的遞歸調用是在java虛擬機棧上完成的.假如a()是一個遞歸方法,那麼在其內部再調用本身的時候,假設爲a1(),那麼a1()方法變量表將建立在a()方法棧幀之上,從而造成了一個新的棧幀.所以容易發現,在遞歸思想中,遞歸簡化了問題的表達,但犧牲了虛擬機棧中的內存空間.java

普通遞歸

斐波那契遞歸法

public static int fib(int num){
        if(num<2)
            return num;
        else
            return fib(num-2)+fib(num-1);
    }
  • 對於上面的解法,很容易就會發現,不但屬於普通遞歸,並且在計算fib(num-1)是重複了fib(num-2)的計算量,所以代碼效率大打折扣.所以效率較高的寫法能夠用for循環計算,
public static int fib3(int n) {
        if (n < 2)
            return n;
        else {
            int pre = 0;
            int suf = 1;
            for (int i = 2; i <= n; i++) {
                int temp = suf;
                suf += pre;
                pre = temp;
            }
            return suf;
        }
    }

斐波那契尾遞歸優化

public class Main {
    public static void main(String[] args) {
        
        System.out.print(fib2(3, 0, 1));
    }


    public static int fib2(int count, int pre, int result) {
        if (count == 1)
            return result;
        else
            return fib2(--count, result, result + pre);
    }
}

性能對比

public static void main(String[] args) {
        long time = new Date().getTime();

        int num=40;
        System.out.println(fib(num));
        System.out.println("普通遞歸調用用時:" + (new Date().getTime() - time) + "毫秒");

        time = new Date().getTime();
        System.out.println(fib2(num, 0, 1));
        System.out.println("尾遞歸優化調用用時:" + (new Date().getTime() - time) + "毫秒");

        time = new Date().getTime();
        System.out.println(fib3(num));
        System.out.println("for循環法調用用時:" + (new Date().getTime() - time) + "毫秒");
    }
    //輸出
    /*
    102334155
    普通遞歸調用用時:674毫秒
    102334155
    尾遞歸優化調用用時:0毫秒
    102334155
    for循環法調用用時:0毫秒
    */
  • 能夠看出有明顯差別,即便普通遞歸法計算量多了一半,時間除以2也是387毫秒,這也遠遠高於for循環和遞歸尾優化法.

尾遞歸優化思想

  • 即遞歸方法return 直接返回方法,注意是直接返回方法,不能是方法加1個值等形式.這樣在遞歸調用時,新方法會覆蓋當前棧幀,達到節省棧空間的目的.所以也就不會有遞歸調用產生的棧溢出問題.

尾遞歸寫法

斐波那契例:
//count做爲計數,表示遞歸層次,
//pre表明前一個值
//result 表示當前值
 public static int fib2(int count, int pre, int result) {
        //層次減到1時返回計算結果
        if (count == 1)
            return result;
        else{
        //遞歸調用時,層次減1,前一項更新爲當前項,因此填result,第三個參數即實現了倒數第二個參數加倒數第一個參數.
        return fib2(--count, result, result + pre);
        }
    }
  • 整體而言參數的書寫分爲兩部分
  • 前部分爲計數,後部分爲計算,例如計算階乘時候只須要兩個參數,第一個計數,第二個存結果.
  • 尾遞歸將所有信息放入了參數裏,所以也就巧妙地避免了須要上一棧幀保存信息.
相關文章
相關標籤/搜索