第三章 遞歸
1 遞歸
大佬說:“如果使用循環,程序的性能可能更高;如果使用遞歸,程序可能更容易理解。如何選擇要看什么對你來說更重要。”
2 基線條件和遞歸條件
每個遞歸函數都有兩部分:基線條件(base case)和遞歸條件(recursive case)。遞歸條件指的是函數調用自己,而基線條件則指的是函數不再調用自己,從而避免形成無限循環。
def countdown(i): #無限循環 print(i) countdown(i-1)
def countdown(i): print(i) if i <= 0: #基線條件 return else: #遞歸條件 countdown(i-1)
3 棧
之前討論數組和鏈表時,有一個待辦事項清單。你可將待辦事項添加到該清單的任何地方,還可刪除任何一個待辦事項。一疊便條要簡單得多:插入的待辦事項放在清單的最前面;讀取待辦事項時,你只讀取最上面的那個,並將其刪除。因此這個待辦事項清單只有兩種操作:壓入(插入)和彈出(刪除並讀取)。
⭐⭐3.1 調用棧(這一塊把棧弄的明明白白)
def greet2(name): print("how are you, " + name + "?") def bye(): print("ok bye!") def greet(name): print("hello, " + name + "!") greet2(name) print("getting ready to say bye...") bye() greet('maggie')
過程如下:調用函數greet('maggie'),計算機將首先為該函數調用分配一塊內存。(不考慮print函數)
然后變量name被設置為maggie,存儲到內存中。
每當你調用函數時,計算機都像這樣將函數調用涉及的所有變量的值存儲到內存中。接下來,你打印 hello, maggie! ,再調用 greet2("maggie") 。同樣,計算機也為這個函數調用分配一塊內存。
PS:⭐這里原來是這樣的 不是共用一個name
計算機使用一個棧來表示這些內存塊,其中第二個內存塊位於第一個內存塊上面。你打印how are you, maggie? ,然后從函數調用返回。此時,棧頂的內存塊被彈出。
現在,棧頂的內存塊是函數 greet 的,這意味着你返回到了函數 greet 。當你調用函數 greet2時,函數 greet 只執行了一部分。這是本節的一個重要概念:調用另一個函數時,當前函數暫停並處於未完成狀態。該函數的所有變量的值都還在內存中。執行完函數 greet2 后,你回到函數greet ,並從離開的地方開始接着往下執行:首先打印 getting ready to say bye… ,再調用函數 bye 。
在棧頂添加了函數 bye 的內存塊。然后,你打印 ok bye! ,並從這個函數返回。
現在你又回到了函數 greet 。由於沒有別的事情要做,你就從函數 greet 返回。這個棧用於存儲多個函數的變量,被稱為調用棧。
練習
3.1 根據下面的調用棧,你可獲得哪些信息?
①調用函數greet,name被設置為‘maggie’
②函數greet中調用greet2,同樣name被設置為‘maggie’
③此時greet函數暫停並處於未完成狀態
④當前調用函數greet2,完成后,greet2結束,從棧頂彈走。
⑤接着執行greet函數
3.2 遞歸調用棧
def fact(x): if x == 1: return 1 else: return x * fact(x-1) a = fact(3) print(a)
遞歸過程如下:
⭐注意,每個 fact 調用都有自己的 x 變量。在一個函數調用中不能訪問另一個的 x 變量。
棧會儲存詳盡的信息但占用大量的內存。有兩種方法改善:
①重寫代碼用while循環
②使用尾遞歸。(這本書沒說,⭐⭐這本結束找下尾遞歸)
練習
3.2 假設你編寫了一個遞歸函數,但不小心導致它沒完沒了地運行。正如你看到的,對於每次函數調用,計算機都將為其在棧中分配內存。遞歸函數沒完沒了地運行時,將給棧帶來什么影響?
棧無休止的擴大,棧空間有限,最終會因為棧溢出而終止,pycharm中有遞歸次數限制。
3.4 小結
遞歸指的是調用自己的函數。
每個遞歸函數都有兩個條件:基線條件和遞歸條件。
棧有兩種操作:壓入和彈出。
所有函數調用都進入調用棧。
調用棧可能很長,這將占用大量的內存。