如何将递归转换为尾递归

时间:2013-04-19 19:46:42

标签: python recursion 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 个答案:

答案 0 :(得分:2)

我不确定是否总是如此,但大多数递归函数都可以实现为尾递归。除了Tail Recursion不同于Tail Recursion优化。

差异尾递归和“常规”

递归函数中必须存在两个元素:

  1. 递归电话
  2. 一个记录返回值的地方。
  3. “常规”递归函数将(2)保留在堆栈帧中。

    常规递归函数中的返回值由两种类型的值组成:

    • 其他返回值
    • 拥有函数计算的结果

    让我们看一个例子:

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

    帧f(5)“存储”它自己的计算结果(5)和f(4)的值。如果我调用factorial(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),依此类推。

    Tail Recursive函数将(2)放在它的参数中。

    在Tail Recursion中,我使用参数将每个递归帧中的部分计算结果传递给下一个递归帧。让我们看看我们的阶乘示例,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)
    

    让我们看看它在factorial(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))
    

    要在Tail递归中转换它,我们:

    • 介绍一个带有累加器的辅助函数
    • 在main函数内运行辅助函数,并将累加器设置为基本情况。

    查找

    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个累加器做了递归辅助函数。一个存储结果,一个存储我正在尝试的位置。

    尾调用优化

    由于没有状态存储在Tail Call堆栈的非边界案例中,因此它们并不那么重要。然后,一些语言/解释器将旧堆栈替换为新堆栈。因此,如果没有堆栈帧限制呼叫数量,尾部呼叫的行为就像一个for循环

    但遗憾的是,Python不是其中之一。当堆栈大于1000时,你会得到一个RunTimeError。Mr. Guido  认为由于尾部调用优化而导致调试目的的清晰度(由抛出的帧引起)比该功能更重要。真是太遗憾了。 Python有很多很酷的功能,尾递归很棒:/