什麼是遞歸?
在高級語言中,調用自己和其它函數沒有本質的不同。我們把一個直接用自己或通過一系列的調用語句間接地調用自己的函數,稱作遞歸函數。每個遞歸函數必須至少有一個條件,滿足時遞歸不再執行,即不再引用自身而是返回值退出。
簡單地說,就是如果在函數中存在着調用函數本身的情況,這種現象就叫遞歸。
遞歸的兩個必要條件
1、存在限制條件,當滿足這個條件時,遞歸便不再繼續。
2、每次遞歸調用之後越來越接近這個限制條件。
示例+圖解
如下簡單示例,我們求一個數n的階乘:
f(n) = n*(n-1)*(n-2) … *2*1
顯然,該式子還可以寫成:
f(n) = n*f(n-1)
因爲f(n)與f(n-1)有關係,即基於其子問題,於是便可以採用“遞歸”求解。
Python代碼:
def func(self, n):
if n == 1:
return 1
else:
return n * self.func(n - 1)
可視化直觀表示即:
遞歸其實可以看做兩部分操作,一步步去尋求子問題的解(直到滿足限制條件,如n==1,得f(1)=1)是“遞”;得到最基本的子問題的解之後,再一步步返回求上一層的解是“歸”。
(如上圖,“遞”的過程中,f(n)=n*f(n-1),結果值都是基於子問題的;但是,到最基本的問題之後,得到了解,“歸”的過程中,每一步都有確定的返回值,直到一層層返回結束,得解。)
因爲,函數的遞歸要利用到“棧”,下圖以“棧”的方式展現“遞歸”過程:
“遞”的過程會將函數的地址、參數等壓棧(push)保存(以便“歸”的時候找到之前執行到的位置),最基本子問題(f(1)=1)求解之後,再一步步“歸”,此過程中,會發生出棧(pop)操作,函數調用結束後,棧頂釋放。
將兩個過程用函數打印出來看一下:
def func(n):
print "遞:%d "%n,
if n==1:
res = 1
else:
res = n*func(n-1)
print "歸:%d "%res,
return res
func(4)
結果:
超簡潔圖解
如果上述兩個圖示還不夠清晰的話,請看第三個:
我們將函數的遞歸調用看做微機原理中的“中斷響應”;
假如圖中“圓環”代表print,且其位於遞歸調用之後(類似中斷響應的斷點之後),那麼最後調用的反而最先print!(根據函數的執行流程,即圖中的箭頭方向) [類似於棧LIFO].
如打印一個鏈表:
# 遞歸打印鏈表
def printListNode(self,lst):
if not lst:
return
# print lst.val,
self.printListNode(lst.next)
print lst.val,
print在遞歸調用之後,則結果(假如單向無環鏈表[1,2,3,4]):
(如果print在斷點之前,則完全相反!)
# 遞歸打印鏈表
def printListNode(self,lst):
if not lst:
return
print lst.val,
self.printListNode(lst.next)
# print lst.val,
print在遞歸調用之後,則結果(假如單向無環鏈表[1,2,3,4]):
附:
二叉樹的先序、中序、後序遍歷之所以得到那樣的結果,也是因函數中print的位置在調用遞歸的前後位置不同造成的。
另外,leetcode中的一道題目也很類似:https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/