小白的算法初识课堂(part3)--递归
學習筆記
學習書目:《算法圖解》- Aditya Bhargava
文章目錄
- 遞歸
- 基線條件和遞歸條件
- 棧
- 調用棧
- 遞歸調用棧
遞歸
首先,我們看一段代碼:
def print_num(my_list):for i in my_list:print(i)print_num([1, 3, 5, 7, 9])輸出:
1 3 5 7 9再看一段代碼:
def print_num2(my_list):if my_list:print(my_list.pop(0))print_num2(my_list)print_num2([1, 3, 5, 7, 9])輸出:
1 3 5 7 9我們看到的第一段代碼使用的是循環,第二段代碼使用的是遞歸,兩種方法結果相同。一般來說,遞歸能讓解決方案更清晰(雖然我舉的例子好像沒體現出來遞歸法更清晰),但并沒有性能上的優勢。實際上,在有些情況下,使用循環的性能更好。
如果使用循環,程序的性能可能更高;如果使用遞歸,程序可能更容易理解。如何選擇要看什么對你來說更重要。
基線條件和遞歸條件
由于遞歸函數調用自己,因此編寫這樣的函數時很容易出錯,進而導致無限循環。例如,假設我要編寫一個像下面這樣倒計時的函數:
def countdown(i): print(i) countdown(i-1)如果我們運行上述代碼,將發現一個問題:這個函數運行起來沒完沒了!
編寫遞歸函數時,必須告訴它何時停止遞歸。正因為如此,每個遞歸函數都有兩部分:基線條件(base case)和遞歸條件(recursive case)。遞歸條件指的是函數調用自己,而基線條件則指的是函數不再調用自己,從而避免形成無限循環。
我們來給countdown函數添加一個基線條件:
def countdown(i): print(i)if i <= 1:returnelse:countdown(i-1)現在,這個函數就會像預期那樣運行.
棧
假設我們有一疊便條,這疊便條記錄著我們馬上要做的待辦事項,我們簡稱這疊便條為清單。當我們插入的待辦事項時,這個事件會放在清單的最上面;當我們讀取待辦事項時,也只讀取清單最上面的那個,且讀完就將其銷毀。因此這個清單只有兩種操作:壓入(插入)和彈出(刪除并讀取)。
這種數據結構被稱為棧。
調用棧
計算機在內部使用被稱為調用棧的棧。
為了演示計算機是如何調用棧的,我們來看下面這個簡單的函數:
def greet(name):print(name, '!')greet2(name)print('too late!')bye()def greet2(name):print(name, '?')def bye():print('bye!')greet('maggie')注意!print是一個函數,但是出于簡化考慮,我們假設它不是函數。
假設,我們調用greet('maggie'),計算機將首先為該函數調用分配一塊內存空間:
變量name被賦值為maggie,這需要存儲到內存中:
當我們調用函數時,計算機會將函數調用涉及的所有變量的值存儲到內存中。接下來,我們再調用greet2('maggie').同樣,計算機也為這個函數調用分配一塊內存。
計算機使用一個棧來表示這些內存塊,其中第二個內存塊位于第一個內存塊上面。我們打印maggie ?,然后從函數greet2的調用返回。此時,棧頂的內存塊被彈出。
現在,棧頂的內存塊是函數greet的,這意味著我們返回到了函數greet。當我調用函數greet2時,函數greet只執行了一部分。調用另一個函數時,當前函數暫停并處于未完成狀態,該函數的所有變量的值仍在內存中。
當執行完greet2函數后,我們繼續向下執行,首先打印too late!,再調用函數bye()。計算機在棧頂添加了函數bye的內存塊,然后我們打印bye!,并從該函數中返回。
現在,我們又回到了greet函數,由于無事可做,我們就從greet函數中返回。這個棧用于存儲多個函數的變量,故被稱為調用棧。
遞歸調用棧
遞歸函數也使用調用棧,我們來看看下面這個遞歸函數fact:
def fact(x):if x == 1:return 1else:return x*fact(x-1)print(fact(3))輸出:
6下面我們來看一下調用fact(3)時,調用棧的變化:
每個fact調用都有自己的x變量,在一個函數調用中不能訪問另一個函數的x變量。
總結
以上是生活随笔為你收集整理的小白的算法初识课堂(part3)--递归的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为 WS832 无线路由器手机设置
- 下一篇: 小白的算法初识课堂(part4)--快速