使用非遞歸來實現遞歸

我們應該遇到過很多次類似的題目了吧: 如何將一個遞歸函數使用非遞歸的方式實現.. 今天突然想到一個通用解, 就是可以在循環中模擬函數調用的方式來實現.

調用棧

以計算 1~n 的和舉例, 遞歸實現如下(Python爲例):

def add_up(n):
    if n <= 1:
        return n
    return n + add_up(n-1)

衆所周知, 函數的調用時通過棧實現的, 每次調用函數的大致流程如下:

  1. 將參數入棧 (還有返回時的 PC, 這裏省略了)
  2. 調用函數
  3. 讀取參數(出棧)
  4. 返回結果入棧
  5. 函數返回, 讀取返回值(出棧)

假設調用函數add_up(4), 那麼棧中的內容大致如下:

image-20230207222845704

這裏爲了方便展示, 對棧中的數據做了簡化(比如 返回地址去掉了, 前後入參拆開展示等等), 不必糾結這些細節, 只要瞭解大致思路即可.

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")

我在註釋中寫的很詳細了, 若有看不懂的可在本地自行調試.


如何, 是不是有點意思? 其實就是將遞歸的函數調用過程自己實現了一次, 依照這個思路, 所有的遞歸函數都可以轉換爲非遞歸的方式. 至於更爲複雜的調用未嘗試, 在此拋磚引玉


原文地址: https://hujingnb.com/archives/886

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章