目錄
第三章 遞歸
3.1 遞歸
遞歸——函數調用自己。
學習如何將問題分成基線條件和遞歸條件。
遞歸會讓解決方案更清晰,並沒有性能上的優勢。實際上,在有些情況下,使用循環的性能更好。
3.2 基線條件和遞歸條件
比如,用遞歸方式編寫倒計時:
def countdown(i):
print i
countdown(i-1)
但運行這個程序,不會自己停止。編寫遞歸函數時,必須告訴它何時停止遞歸。
每個遞歸函數都有兩部分:基線條件(base case)和遞歸條件(recursive case)。遞歸條件指的是函數調用自己,而基線條件則指的是函數不再調用自己,從而避免形成無限循環。
def countdown(i):
print (i)
if i <= 0: #基線條件
return
else:
countdown(i-1) #遞歸條件
countdown(5)
Out:
5 4 3 2 1 0
3.3 棧
用一疊便條寫待辦事項,待辦事項清單隻有兩種操作:壓入(插入)和彈出(刪除並讀取)。
這種數據結構稱爲棧。
3.3.1 調用棧
def greet(name):
print "hello, " + name + "!"
greet2(name)
print "getting ready to say bye..."
bye()
greet函數要依次調用greet2,bye函數。
調用greet("maggie"),計算機爲greet函數分配一個內存,變量名爲“maggie”存儲到內存中。
接着運行print(),再接着調用greet2函數。(調用另一個函數時,當前函數暫停並處於未完成狀態。)計算機也爲greet2分配內存。
這個函數調用結束後,棧頂的內存塊被彈出。再調用下個函數,最後再返回最初的greet函數。
由於沒有別的事情要做,你就從函數greet返回。這個棧用於存儲多個函數的變量,被稱爲調用棧。
練習1
3.1 根據下面的調用棧,你可獲得哪些信息?
答:調用函數GREET,變量名爲MAGGIE,在此過程中函數GREET2函數被調用,計算機爲它分配內存,堆疊上去,當GREET2調用結束時該內存塊會被彈開,返回到GREET函數的調用中。
3.3.2 遞歸調用棧
計算階乘。
#計算階乘
def fact(x):
if x == 1: #基線條件
return 1
else:
return x*fact(x-1) #遞歸條件
print(fact(5))
Out: 120
使用棧雖然很方便,但是也要付出代價:存儲詳盡的信息可能佔用大量的內存。每個函數調用都要佔用一定的內存,如果棧很高,就意味着計算機存儲了大量函數調用的信息。在這種情況下,你有兩種選擇。
重新編寫代碼,轉而使用循環。
使用尾遞歸。這是一個高級遞歸主題,不在本書的討論範圍內。另外,並非所有的語言都支持尾遞歸。
練習2
假設你編寫了一個遞歸函數,但不小心導致它沒完沒了地運行。正如你看到的,對於每次函數調用,計算機都將爲其在棧中分配內存。遞歸函數沒完沒了地運行時,將給棧帶來什麼影響?
答:調用棧不斷加層,會越來越長,直至計算機內存不夠。
(在Python中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。---百度百科)
3.4 小結
遞歸指的是調用自己的函數。
每個遞歸函數都有兩個條件:基線條件和遞歸條件
棧有兩種操作:壓入和彈出。
所有函數調用都進入調用棧。
調用棧可能很長,這將佔用大量的內存。