什麼是遞歸?
通俗來講:遞歸就是方法自己調用自己,通過每次傳入的參數不同,可以解決複雜的問題。
爲什麼需要遞歸
遞歸算法可以把本身問題分解規模小的同類問題,通過求解規模小的同類問題的解,之後不斷進行返回值,最終可以求得規模大的問題。
來看看遞歸的優缺點:
優點 | 缺點 |
---|---|
1. 邏輯性好;2. 可讀性好;3.代碼簡潔。 | 1. 由於遞歸需要利用棧,所以佔用空間大,可能會發生棧溢出;2. 會存在重複計算,需要進行優化。 |
遞歸的三大要素
第一要素: 明確你這個函數想要幹什麼。先不管函數裏面的代碼什麼,而是要先明白,你這個函數的功能是什麼,要完成什麼樣的一件事。
第二要素: 尋找遞歸結束條件。我們需要找出當參數爲啥時,遞歸結束,之後直接把結果返回,請注意,這個時候我們必須能根據這個參數的值,能夠直接知道函數的結果是什麼。
第三要素: 找出函數的等價關係式。我們要不斷縮小參數的範圍,縮小之後,我們可以通過一些輔助的變量或者操作,使原函數的結果不變。
遞歸的過程
上面的遞歸的過程比較抽象,接下來自己慢慢講:
需要了解的知識點
**棧:**棧是一種數據結構,可以實現先進後出的功能。我們寫的程序需要調用線程來執行,如下圖,通過查看JVM的內存管理模型,可以知道虛擬機棧是線程私有的,其中虛擬機棧的作用就是每次方法調用的數據需要通過棧來傳遞。
遞歸的調用機制
- 利用階乘問題講解遞歸的調用機制:
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刷完了:
參考