【算法】遞歸
遞歸
實例:在盒子中尋找鑰匙
方法1:使用while循環
算法
(1) 創建一個要查找的盒子堆。
(2) 從盒子堆取出一個盒子,在裏面找。
(3) 如果找到的是盒子,就將其加入盒子堆中,以便以後再查找。
(4) 如果找到鑰匙,則大功告成!
(5) 回到第二步。
代碼
def look_for_key(main_box): pile=main_box.make_a_pile_to_look_through() while pile is not empty: box=pile.grap_a_box for item in box: if item.is_a_box(): pile.append(item) elif item.is_a_key(): print(‘found a key‘)
方法2:使用遞歸
算法
(1) 檢查盒子中的每樣東西。
(2) 如果是盒子,就回到第一步。
(3) 如果是鑰匙,就大功告成!
代碼
def look_for_key(box): for item in box: if item.is_a_box(): look_for_key(item) #遞歸 elif item.is_a_key(): print(‘found a key‘)
如果使用循環,程序的性能可能更高;如果使用遞歸,程序可能更容易理解。如何選擇要看什麽對你來說更重要。
基線條件和遞歸條件
編寫遞歸函數時,必須告訴它何時停止遞歸。否則會導致無限循環。
遞歸函數兩部分
基線條件(base case)和遞歸條件(recursive case)
遞歸條件:指的是函數調用自己
基線條件:指的是函數不再調用自己,從而避免形成無限循環。
示例
def countdown(i): print(i) if i<=0: #基線條件 return else: #遞歸條件 countdown(i-1) print(countdown(4))
棧
定義
棧(stack)又名堆棧,它是一種運算受限的線性表。其限制是僅允許在表的一端進行插入和刪除運算。
特點
LIFO,即後進先出(Last in, first out)
調用棧(call stack)
計算機在內部使用被稱為調用棧的棧。
示例
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() print(greet(‘lilip‘))
調用過程
- 調用greet(‘lilip’),首先為該函數調用分配一塊內存
- 打印,hello,lilip!
- 調用greet2(‘lilip’),接著為這個函數調用分配一塊內存
- 計算機使用一個棧表示這些內存塊,其中第二個內存塊位於第一個內存塊的上面。
- 打印,how are you,lilip?greet2函數運行完畢,然後從函數調用返回。此時,棧頂的內存塊被彈出。
- 棧頂內存塊是函數greet,返回到函數greet。
- 打印getting ready to say bye..
- 調用bye()函數,棧頂添加函數bye的內存塊
- 打印,ok bye!並從這個函數返回。
- 返回到函數greet,此時運行完畢。從函數greet返回,
調用另一個函數時,當前函數暫停並處於未完成狀態。該函數的所有變量的值都還在內存中
這個棧用於存儲多個函數的變量,被稱為調用棧。
遞歸調用棧
遞歸函數也使用調用棧!
示例
階乘遞歸函數
def fact(x): if x==1: return 1 else: return x*fact(x-1) print(fact(3))
調用過程
代碼 |
調用棧 |
備註 |
||||||||||||||||||
fact(3) |
|
第一次調用fact x=3 |
||||||||||||||||||
if x==1: |
|
|
||||||||||||||||||
else: |
|
|
||||||||||||||||||
return x*fact(x-1) |
|
遞歸調用 |
||||||||||||||||||
if x==1: |
|
現在位於對fact的第二次調用中 x=2 最上面的函數調用是當前所處的調用 |
||||||||||||||||||
else: |
|
兩個函數調用都有變量x,但這些x變量的值不同 |
||||||||||||||||||
return x*fact(x-1) |
|
在棧頂層調用中,不能訪問下面其他內存塊調用的x變量,反之亦然 |
||||||||||||||||||
if x==1: |
|
|
||||||||||||||||||
return 1 |
|
從棧中彈出第一個方塊,意味著將從這個調用返回,返回1 |
||||||||||||||||||
return x*fact(x-1) |
|
x為2 fact(x-1)返回1 最終返回2 |
||||||||||||||||||
return x*fact(x-1) |
|
x為3 fact(x-1)返回2 最終返回6 |
註意:
- 每個fact調用都有自己的x變量。在一個函數調用中不能訪問另一個的x變量。
- 每個函數調用都要占用一定的內存,如果棧很高,就意味著計算機存儲了大量函數調用的信息。
小結
- 遞歸指的是調用自己的函數。
- 每個遞歸函數都有兩個條件:基線條件和遞歸條件。
- 棧有兩種操作:壓入和彈出。
- 所有函數調用都進入調用棧。
- 調用棧可能很長,這將占用大量的內存。
【算法】遞歸