真的懂遞歸嗎?

什麼是遞歸?

通俗來講:遞歸就是方法自己調用自己,通過每次傳入的參數不同,可以解決複雜的問題。

爲什麼需要遞歸

遞歸算法可以把本身問題分解規模小的同類問題,通過求解規模小的同類問題的解,之後不斷進行返回值,最終可以求得規模大的問題。

來看看遞歸的優缺點:

優點 缺點
1. 邏輯性好;2. 可讀性好;3.代碼簡潔。 1. 由於遞歸需要利用棧,所以佔用空間大,可能會發生棧溢出;2. 會存在重複計算,需要進行優化。

遞歸的三大要素

第一要素: 明確你這個函數想要幹什麼。先不管函數裏面的代碼什麼,而是要先明白,你這個函數的功能是什麼,要完成什麼樣的一件事。

第二要素: 尋找遞歸結束條件。我們需要找出當參數爲啥時,遞歸結束,之後直接把結果返回,請注意,這個時候我們必須能根據這個參數的值,能夠直接知道函數的結果是什麼。

第三要素: 找出函數的等價關係式。我們要不斷縮小參數的範圍,縮小之後,我們可以通過一些輔助的變量或者操作,使原函數的結果不變。

遞歸的過程

img

上面的遞歸的過程比較抽象,接下來自己慢慢講:

需要了解的知識點

**棧:**棧是一種數據結構,可以實現先進後出的功能。我們寫的程序需要調用線程來執行,如下圖,通過查看JVM的內存管理模型,可以知道虛擬機棧是線程私有的,其中虛擬機棧的作用就是每次方法調用的數據需要通過棧來傳遞。

img

遞歸的調用機制

  1. 利用階乘問題講解遞歸的調用機制:
class Solution{
    public static void main(String[] args){
        int res = factorial(3);
    }
    //第一步,明確函數的功能
    public static int factorial(int n){
        //第二步,遞歸的結束條件
        if(n == 1){
            return 1;
        }else{
            //第三步,函數的等價關係式
            return factorial(n - 1) * n;
        }
    }
}

圖解:方法遞歸時,實際內存運行過程。
在這裏插入圖片描述

需要遵守的規則

  • 執行一個方法時,就會創建一個新的獨立棧空間。
  • 方法的局部變量是獨立的,不會相互影響,比如程序中的變量n。
  • 如果方法中使用的是引用類型的變量(比如數組),就會共享該引用類型的數據。
  • 遞歸必須要向退出遞歸的條件逼近,否則就會無限遞歸,會出現StackOverflowError
  • 當一個方法執行完畢,或者遇到return,就會返回,遵循誰調用,就將結果返回給誰

實例2:利用遞歸反轉鏈表

反轉鏈表,如對鏈表1->2->3->4 進行反轉爲4->3->2->1

首先定義鏈表的節點

class Node{
    int val;
    Node next;
}

按照遞歸的三要素來

第一步:定義函數的功能

設函數reverseLinkedList(head)的功能是反轉鏈表,其中head表示鏈表的頭節點。

Node reverseLinkedList(Node head){
	
}

第二步:遞歸的結束條件

當鏈表沒有節點或只有一個節點時,此時不用進行反轉,這個作爲遞歸的結束條件。

Node reverseLinkedList(Node head){
    if(head == null || head.next == null){
        return head;
    }
}

第三步:尋找等價條件

這個有一定的難度,根據需要遵守的規則,在找等價條件的過程中,一定是要逼近遞歸的結束條件。

Node reverseLinkedList(Node head){
    if(head == null || head.next == null){
        return head;
    }
    //不斷去移動指針,不斷去逼近結束條件。
    Node newList = reverseLinkedList(head.next);
}

根據上述求階乘的例子,可以知道此時的內存運行過程,通過不斷地移動指針(head.next),最終只用一個節點,如,通過過程,對於1->2->3->4鏈表此時爲:
在這裏插入圖片描述
接下來就是過程了,交換指針的指向。

//用遞歸的方法反轉鏈表
public static Node reverseList2(Node head){
    // 1.遞歸結束條件
    if (head == null || head.next == null) {
             return head;
         }
         // 遞歸反轉 子鏈表
         Node newList = reverseList2(head.next);
         // 改變 3,4節點的指向。
         // 通過 head.next獲取節點4
         Node t1  = head.next;
         // 讓 4 的 next 指向 4
         t1.next = head;
         // 3 的 next 指向 null.
        head.next = null;
        // 把調整之後的鏈表返回。
        return newList;
    }

第一次歸的過程
在這裏插入圖片描述
當歸過程結束可實現反轉
在這裏插入圖片描述

優化

後續。。。

總結

對於初學者來講,理解遞歸是比較有難度的,但是隻要去了解其中的思想,多做一些練習是可以掌握的。

另外,自己也在學習算法,自己將劍指offer刷完了:

劍指offer

參考

  1. https://baijiahao.baidu.com/s?id=1629571574350179349&wfr=spider&for=pc
  2. https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/jvm/Java內存區域.md
  3. https://www.zhihu.com/question/31412436
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章