- 需求背景、限制條件、化簡
- 模擬盤子的移動步驟
- 遞迴實現Code
- 分析
- 練習 1
需求背景、限制條件、化簡
漢諾塔就是一個由柱子和盤子組成的玩具,它有一些玩法上的限制,主要是規定了盤子移動有限制。
想理解到遞迴本質,漢諾塔是個不錯的載體。
怎麼體會?
在盤子移動的過程中。
# 盤子的數量:
# 通常,我們假設有 n 個盤子。盤子在初始時按從大到小的順序疊放在一個柱子上(源柱子)。
# 這裡採用數字表示盤子,數字大小表示盤子大小。
# 柱子數量:
# 總共有三個柱子:源柱子(起始柱子)、目標柱子(最終柱子) 和輔助柱子(中間柱子)。
# 這裡用a,b,c list模擬三個柱子
# 規定list只能從末尾取出
# 移動規則:
# 每次只能移動一個盤子。
# 任何時刻盤子只能放在一個柱子上。
# 大的盤子不能放在小的盤子上。
# 目標:
# 將所有的盤子從源柱子移動到目標柱子,且遵守上述規則。
總結:
- 瞭解了問題和目標;
- 將現實問題抽出,化簡,採用符號化方式去表達;
模擬盤子的移動步驟
先正常演示一次移動:
n = 1
a = [1]
b = []
c = []
a = []
b = []
c = [1]
n = 2
a = [2,1]
b = []
c = []
(1)
a = [2]
b = [1]
c = []
(2)
a = []
b = [1]
c = [2]
(3)
a = []
b = []
c = [2,1]
**
n = 3
a = [3,2,1]
b = []
c = [1]
(1)
a = [3,2]
b = []
c = [1]
(2)
a = [3]
b = [2]
c = [1]
(3)
a = [3]
b = [2,1]
c = []
(4)
a = []
b = [2,1]
c = [3]
(5)
a = [1]
b = [2]
c = [3]
(6)
a = [1]
b = []
c = [3,2]
(7)
a = []
b = []
c = [3,2,1]
上面正向的思考,n約大,步驟越多,越來越亂, 抓不住重點。
必須得以反向思維思考:
n = 3
a = [3]
b = [2,1]
c = []
a = []
b = [2,1]
c = [3]
**
n = 4
a = [4]
b = [3,2,1]
c = []
a = []
b = [3,2,1]
c = [4]
**
n = 5
a = [5]
b = [4,3,2,1]
c = []
a = []
b = [4,3,2,1]
c = [5]
......
簡單的抓到一些固定特徵:
-
不管n是幾,都是a到c.
-
如果n>1,就把n-1的盤子都放到輔助柱子,n 直接去 c。(這個也是劃分出來的一個子問題)
遞迴實現Code
def hanoi(n: int, a: list, b: list, c: list):
print("sub State:", a, b, c)
if n == 1:
# 基準情況
c.append(a.pop())
print("State 1:", a, b, c)
else:
# 遞迴情況
hanoi(n - 1, a, c, b)
c.append(a.pop())
print("State 2:", a, b, c)
hanoi(n - 1, b, a, c)
a = [3, 2, 1]
b = []
c = []
print("Initial state:", a, b, c)
hanoi(len(a), a, b, c)
print("Final state:", a, b, c)
核心:
在遞迴呼叫中,柱子的角色(源、目標、輔助)不斷交換。這種角色交換保證了每一步都能正確地完成盤子的移動。
out:
Initial state: [3, 2, 1] [] []
sub State: [3, 2, 1] [] []
sub State: [3, 2, 1] [] []
sub State: [3, 2, 1] [] []
State 1: [3, 2] [] [1]
State 2: [3] [1] [2]
sub State: [1] [3] [2]
State 1: [] [3] [2, 1]
State 2: [] [2, 1] [3]
sub State: [2, 1] [] [3]
sub State: [2, 1] [3] []
State 1: [2] [3] [1]
State 2: [] [1] [3, 2]
sub State: [1] [] [3, 2]
State 1: [] [] [3, 2, 1]
Final state: [] [] [3, 2, 1]
分析
採用視覺化方式觀察:
https://pythontutor.com/
看完的的一些體會:
-
函式會巢狀函式, 多個巢狀函式之間一起組成一個整體的函式;
-
遞迴透過傳遞下去的變數,在去 回之間的改變,能達到一個很炸裂的效果;
-
遞迴的層下沉,一般使用一個int變數控制。
練習 1
你有一個儲存員工資訊的資料結構,它包含了員工唯一的 id ,重要度和直系下屬的 id 。
給定一個員工陣列 employees,其中:
- employees[i].id 是第 i 個員工的 ID。
- employees[i].importance 是第 i 個員工的重要度。
- employees[i].subordinates 是第 i 名員工的直接下屬的 ID 列表。
給定一個整數 id 表示一個員工的 ID,返回這個員工和他所有下屬的重要度的 總和。
# Definition for Employee.
class Employee:
def __init__(self, id: int, importance: int, subordinates: List[int]):
self.id = id
self.importance = importance
self.subordinates = subordinates