簡體   English   中英

如何將遞歸轉換為尾遞歸

[英]How to Convert Recursion to Tail Recursion

是否總是有可能將遞歸轉換為尾遞歸?

我很難將以下Python函數轉換為尾遞歸函數。

def BreakWords(glob):
  """Break a string of characters, glob, into a list of words.

  Args:
    glob: A string of characters to be broken into words if possible.

  Returns:
    List of words if glob can be broken down. List can be empty if glob is ''.
    None if no such break is possible.
  """
  # Base case.
  if len(glob) == 0:
    return []

  # Find a partition.
  for i in xrange(1, len(glob) + 1):
    left = glob[:i]
    if IsWord(left):
      right = glob[i:]
      remaining_words = BreakWords(right)
      if remaining_words is not None:
        return [left] + remaining_words

  return None

我不確定是否總是這樣,但是大多數遞歸函數可以實現為尾遞歸。 此外,尾遞歸與尾遞歸優化不同。

差異尾遞歸和“常規”

遞歸函數中必須包含兩個元素:

  1. 遞歸調用
  2. 一個保留返回值計數的地方。

“常規”遞歸函數將(2)保留在堆棧幀中。

常規遞歸函數中的返回值由兩種類型的值組成:

  • 其他返回值
  • 擁有函數計算的結果

讓我們來看一個例子:

def factorial(n):
    if n == 1 return 1
    return n * factorial(n-1)

例如,幀f(5)“存儲”它自己的計算結果(5)和f(4)的值。 如果我調用階乘(5),就在堆棧調用開始崩潰之前,我有:

 [Stack_f(5): return 5 * [Stack_f(4): 4 * [Stack_f(3): 3 * ... [1[1]]

請注意,除了我提到的值之外,每個堆棧還存儲函數的整個范圍。 因此,遞歸函數f的內存使用量為O(x),其中x是我必須進行的遞歸調用數。 因此,如果我需要1kb的RAM來計算階乘(1)或階乘(2),則需要〜100k來計算階乘(100),依此類推。

尾遞歸函數將(2)放在其參數中。

在尾部遞歸中,我使用參數將每個遞歸幀中的部分計算結果傳遞給下一個遞歸幀。 讓我們看一下我們的析因示例Tail Recursive:

def factorial(n):
    def tail_helper(n, acc):
        if n == 1 or n == 2: return acc
        return tail_helper(n-1, acc + n)
return tail_helper(n,0)

讓我們看一下階乘(4)中的幀:

[Stack f(4, 5): Stack f(3, 20): [Stack f(2,60): [Stack f(1, 120): 120]]]]

看到差異了嗎? 在“常規”遞歸調用中,返回函數以遞歸方式組成最終值。 在Tail Recursion中,它們僅引用基本案例(評估的最后一個案例) 我們稱累加器為跟蹤較早值的參數。

遞歸模板

常規的遞歸函數如下:

def regular(n)
    base_case
    computation
    return (result of computation) combined with (regular(n towards base case))

要在尾部遞歸中對其進行轉換,我們:

  • 介紹帶有累加器的輔助功能
  • 在累加器設置為基本情況的情況下,在主函數中運行輔助函數。

看:

def tail(n):
    def helper(n, accumulator):
        if n == base case:
            return accumulator
        computation
        accumulator = computation combined with accumulator
        return helper(n towards base case, accumulator)
    helper(n, base case)

你的例子:

我做了這樣的事情:

def BreakWords(glob):
    def helper(word, glob, acc_1, acc_2):
        if len(word) == 0 and len(glob) == 0:
            if not acc_1:
                return None
            return acc
        if len(word) == 0:
            word = glob.pop[0]
            acc_2 = 0

        if IsWord(word.substring[:acc_2]):
            acc_1.append(word[:acc_2])
            return helper(word[acc_2 + 1:], glob, acc_1, acc_2 + 1)

        return helper(word[acc_2 + 1:], glob, acc_1, acc_2 + 1)

    return helper("", glob, [], 0)

為了消除您所做的for語句,我使用2個累加器執行了遞歸輔助函數。 一種用於存儲結果,一種用於存儲我當前正在嘗試的位置。

尾部呼叫優化

由於沒有狀態存儲在尾部調用堆棧的非邊界情況下,因此它們並不是那么重要。 然后,某些語言/解釋器將舊堆棧替換為新堆棧。 因此,在沒有堆棧幀限制調用次數的情況下, 尾部調用的行為就像一個for循環

但是不幸的是,Python並不是其中一種情況。 當堆棧大於1000時,您將收到RunTimeError。Guido 先生認為,由於尾部調用優化(由拋出的幀引起)而導致調試目的的清晰度比該功能更為重要。 真可惜 Python有很多很棒的功能性東西,並且在它上面尾部遞歸將是很棒的:/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM