遞歸與循環

一.遞歸與循環

遞歸,說白了就是自己調用自己。理論上,任何的循環都可以重寫爲遞歸形式,所有的遞歸也可以被表述成循環的形式,本文主要介紹如何將循環重寫爲遞歸。

二.循環改遞歸的兩大要點

  • 發現邏輯“相似性”
  • 不要忘記遞歸的“出口”

小例子:

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

上述代碼的輸出結果是打印出數字“0-9”。

這是利用循環實現,接下來我們將循環改寫爲遞歸:

public class Main{
    public static void f(int n){
        if(n<0) return;//出口
        f(n-1);//打印0 ~ n-1
        System.out.println(n);//打印n
    }
    public static void main(String[] args){
       f(9);
    }
}

思路解釋:“踢皮球”-常用來形容政府職能部門職責不清,相互推諉,比如你要到部門A蓋個章,A叫你先去部門B蓋章,B叫你去部門C蓋章...這種將工作以踢皮球的方式甩給其他人做的方式其實很適用於遞歸。

但在遞歸中,如果你甩給其他人的工作和分配給你的工作完全一樣,則程序陷入死循環,一直沒有人進行工作,一直在甩鍋,因此,遞歸中爲:自己做一小部分事情,剩下的事情再甩給其他人去做。

我們要做的是打印除數字“0 - 9”,那麼,我完全可以自己負責打印其中一個數(代碼中表現爲打印末尾數字9),將剩下的數字“0 - 8”交予其他人去打印,比如A打印9,B負責打印“0-8”,B又打印8,並將“0-7”甩給C去負責,C又打印7,剩下的甩給D......

每次甩給下個人的工作都少了一點,在代碼中表現爲每次進行調用的參數都不一致,這就是發現邏輯“相似性”,每次調用的都是f(int n)這個方法,但每次參數都發生了變化,即f(n-1)

但若一直遞歸下去,程序將陷入死循環,當n=0時,又會調用f(-1),這顯然不正確,因此,我們需要給遞歸加上一個出口限制,即if(n<0) return;,當n<0時,代表0~9都已經打印完畢,我們需要終止遞歸。

:並不一定非得A打印9,將“0~8” 甩給B...也可以A負責打印首位0,將“1~9”甩給B...這種情況有些許不一樣(其實就是參數發生了變化),代碼如下:

public static void f2(int begin,int end){
    if(begin>end) return;
    System.out.println(begin);
    f2(begin+1,end);
}

由上可看出,循環轉遞歸,可能並非只有一種解法,也可存在多種改寫形式,但都遵循“相似性”+“出口”的原則。

三.構造相似性

  • 如果沒有明顯的相似性,需要主動構造
  • 不能相似的原因很可能是缺少參數
  • 遞歸與數學上的遞推公式很類似

在循環轉遞歸的過程中,可能並不具有非常明顯的相似性,很可能是因爲缺少參數的原因,這時候就需要我們去主動構造相似性,通常爲增加參數來實現。

例子1:

public class demo{
    public static int addAll(int[] a){
        int x=0;
        for(int i=0;i<a.length;i++){
            x+=a[i];
        }
        return x;
    }
    public static void main(String[] args){
        int[] a={3,4,11,8};
        int sum=addAll(a);
        System.out.println(sum);
    }
}

上述代碼的功能爲將數組a中的所有元素求和並打印。接下來,將循環改寫爲遞歸:

public class Main{
    public static int f(int[] a,int begin){
        if(begin==a.length) return 0;//出口
        int x=f(a,begin+1);
        return x+a[begin];
    }
    public static void main(String[] args){
        int[] a={3,4,11,8};
        int sum=f(a,0);
        System.out.println(sum);
    }
}

轉爲遞歸求和,我們一開始想到的肯定是定義一個求和方法,方法內傳入要求和的數組,即 f(int[] a) ,但這樣踢皮球每次甩給別人的都是原封不動的任務,導致程序進入死循環,因此我們需要加入一個參數 int begin 作爲當前對象負責求和的起始元素的下標,這樣,A只需要將 begin+1~end 的求和工作甩給B去做,B只需要再將拿到的begin進行+1操作,然後甩給C去做......待B傳回求和的值,A再將B傳回的值加上a[0]就可以得到數組所有元素之和了。

:除了上述解法之外,還有以下解法:

  1. [0......end-1] [end],將數組拆成兩部分,A負責end,將0~end-1甩給B去做;

  2. 折半求和:

    public class Main{
        public static int f(int[] a,int begin,int end){
         if(begin>end) return 0;
         int middle = (begin+end)/2; //取中值
         if(begin==end){
             return a[end];
         }
         return f(a,begin,middle)+f(a,middle+1,end);     
        }
    
        public static void main(String[] args){
            int[] a={3,4,11,8};
            int sum=f(a,0,a.length-1);
            System.out.println(sum);
        }
    }
    

例子2:

public class demo{
    public static boolean isSameString(String s1,String s2){
        return s1.equals(s2);
    }
    public static void main(String[] args){
        System.out.println(isSameString("abc","abcd"));
    }
}

上述代碼的功能爲比較兩個字符串是否相同。接下來,將循環改寫爲遞歸:

public class Main {
    public static boolean f(String s1, String s2) {
        if(s1.length()!=s2.length()) {
            return false;
        }
        if(s1.length()==0) {//出口:當比較至兩字符串爲空時表示兩字符串各字符均相同
            return true;
        }
        if(s1.charAt(0)!=s2.charAt(0)) {
            return false;
        }
        return f(s1.substring(1),s2.substring(1));
    }
    public static void main(String[] args) {
        System.out.println(f("abc", "abcd"));
    }
}

四.遞歸調用

  • 遞歸調用僅僅是被調用函數恰爲主調函數(自己調用自己)
  • 注意每次調用的層次不同
  • 注意每次分配形參並非同一個變量
  • 注意返回的次序

如圖所示:A運行到節點a,調用B,B運行到節點b,調用C,C執行結束之後,將結果返回節點b,B從節點b開始繼續運行,B執行結束之後將結果返回節點a,A再從節點a開始繼續運行...

該執行過程要求每一次節點中斷調用其他方法,必須記錄下該中斷節點的環境信息,作用是爲了調用結束返回結果之後原程序能夠從上次中斷位置起繼續執行,在計算機中,通過一個結構來實現該效果(棧:先進後出):

A執行至a時將要去調用B,此時將節點a的環境信息通過壓棧的方式保存在棧結構中,同理,在b節點處又將節點b的環境信息通過壓棧的方式保存在棧結構中;

當C執行結束返回結果給B時,通過彈棧的方式將b進行出棧操作,這樣B就可以從b開始繼續運行,同理,當B執行完畢返回結果給A時,通過彈棧的方式將a進行出棧操作,這樣A就可以從a開始繼續運行。

逐層深入,逐層返回。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章