我們應該遇到過很多次類似的題目了吧: 如何將一個遞歸函數使用非遞歸的方式實現.. 今天突然想到一個通用解, 就是可以在循環中模擬函數調用的方式來實現.
調用棧
以計算 1~n
的和舉例, 遞歸實現如下(Python
爲例):
def add_up(n):
if n <= 1:
return n
return n + add_up(n-1)
衆所周知, 函數的調用時通過棧實現的, 每次調用函數的大致流程如下:
- 將參數入棧 (還有返回時的 PC, 這裏省略了)
- 調用函數
- 讀取參數(出棧)
- 返回結果入棧
- 函數返回, 讀取返回值(出棧)
假設調用函數add_up(4)
, 那麼棧中的內容大致如下:
這裏爲了方便展示, 對棧中的數據做了簡化(比如 返回地址去掉了, 前後入參拆開展示等等), 不必糾結這些細節, 只要瞭解大致思路即可.
當add_up(1)
返回之後, 再將所有數據依次出棧得到最終結果.
模擬棧調用
好, 既然函數調用就是對棧的一系列操作, 那麼回到我們最開始的問題: 如何用非遞歸的方式來實現遞歸函數. 是不是有思路了? 只要在內存中模擬函數調用的過程, 就可以將其無縫轉換了.
話不多說, 直接上代碼:
def add_up_by_stack(n):
# 沒必要進入遞歸
if n <= 1:
return 1
stack = []
down = 1 # 向下調用函數標記
up = -1 # 函數返回標記
# 入棧內容依次爲: 局部變量, 函數參數, 函數返回值, 調用方向
stack.append((n, n - 1, 0, down))
while len(stack) > 0:
# 從棧中讀取上一級放入的內容
(num, param_num, return_num, operate) = stack.pop()
# 當前是函數調用 return 階段, 且已經到最後一級了, 因此直接返回結果即可
if operate is up and len(stack) == 0:
# 結果爲 當前的局部變量 + 函數返回值
# 對應了遞歸中的 n + return_n
return num + return_num
# 開始函數調用模擬
if operate is down: # 調用下一級
if param_num <= 1: # 返回結果
# 模擬 if n <= 1: return 1
# 將結果返回給上一級
stack.append((num, param_num, 1, up))
else:
# 模擬 return n + add_up(n-1)
# 因爲當前內容還沒有調用完, 需要向下調用. 因此將當前級別放回去
# 但是放回去時, 要將 down 改爲 up, 已經到達 return 了, 下次回來直接返回
stack.append((num, param_num, return_num, up))
# 調用下一級, 將數據入棧
stack.append((param_num, param_num - 1, 0, down))
else:
# 當前爲遞歸返回階段, 則需要將當前函數的返回值返回給調用方
# 將上一層數據取出, 並將返回結果放進去
# 對應了遞歸中的 n + return_n
(last_num, last_param_num, last_return_num, last_operate) = stack.pop()
stack.append((last_num, last_param_num, num + return_num, last_operate))
# 不會走到這裏
raise Exception("error")
我在註釋中寫的很詳細了, 若有看不懂的可在本地自行調試.
如何, 是不是有點意思? 其實就是將遞歸的函數調用過程自己實現了一次, 依照這個思路, 所有的遞歸函數都可以轉換爲非遞歸的方式. 至於更爲複雜的調用未嘗試, 在此拋磚引玉