简体   繁体   English

如何将递归转换为尾递归

[英]How to Convert Recursion to Tail Recursion

Is it always possible to convert a recursion into a tail recursive one? 是否总是有可能将递归转换为尾递归?

I am having a hard time converting the following Python function into a tail-recursive one. 我很难将以下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

I'n not sure if is always the case, but most of recursive functions can be implemented as tail recursives. 我不确定是否总是这样,但是大多数递归函数可以实现为尾递归。 Besides Tail Recursion is different from Tail Recursion optimization. 此外,尾递归与尾递归优化不同。

Differences Tail Recursion and "Regular" ones 差异尾递归和“常规”

There are two elements that must be present in a recursive function: 递归函数中必须包含两个元素:

  1. The recursive call 递归调用
  2. A place to keep count of the return values. 一个保留返回值计数的地方。

A "regular" recursive function keeps (2) in the stack frame. “常规”递归函数将(2)保留在堆栈帧中。

The return values in regular recursive function are composed of two types of values: 常规递归函数中的返回值由两种类型的值组成:

  • Other return values 其他返回值
  • Result of the owns function computation 拥有函数计算的结果

Let's see a example: 让我们来看一个例子:

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

The frame f(5) "stores" the result of it's own computation (5) and the value of f(4), for example. 例如,帧f(5)“存储”它自己的计算结果(5)和f(4)的值。 If i call factorial(5), just before the stack calls begin to colapse, i have: 如果我调用阶乘(5),就在堆栈调用开始崩溃之前,我有:

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

Notice that each stack stores, besides the values i mentioned, the whole scope of the function. 请注意,除了我提到的值之外,每个堆栈还存储函数的整个范围。 So, the memory usage for a recursive function f is O(x), where x is the number of recursive calls i have to made. 因此,递归函数f的内存使用量为O(x),其中x是我必须进行的递归调用数。 So, if i needb 1kb of RAM to calculate factorial(1) or factorial(2), i need ~100k to calculate factorial(100), and so on. 因此,如果我需要1kb的RAM来计算阶乘(1)或阶乘(2),则需要〜100k来计算阶乘(100),依此类推。

A Tail Recursive function put (2) in it's arguments. 尾递归函数将(2)放在其参数中。

In a Tail Recursion, i pass the result of the partial calculations in each recursive frame to the next one using parameters. 在尾部递归中,我使用参数将每个递归帧中的部分计算结果传递给下一个递归帧。 Let's see our factorial example, Tail Recursive: 让我们看一下我们的析因示例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)

Let's look at it's frames in factorial(4): 让我们看一下阶乘(4)中的帧:

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

See the differences? 看到差异了吗? In "regular" recursive calls the return functions recursively compose the final value. 在“常规”递归调用中,返回函数以递归方式组成最终值。 In Tail Recursion they only reference the base case (last one evaluated) . 在Tail Recursion中,它们仅引用基本案例(评估的最后一个案例) We call accumulator the argument that keeps track of the older values. 我们称累加器为跟踪较早值的参数。

Recursion Templates 递归模板

The regular recursive function go as follows: 常规的递归函数如下:

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

To transform it in a Tail recursion we: 要在尾部递归中对其进行转换,我们:

  • Introduce a helper function that carries the accumulator 介绍带有累加器的辅助功能
  • run the helper function inside the main function, with the accumulator set to the base case. 在累加器设置为基本情况的情况下,在主函数中运行辅助函数。

Look: 看:

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)

Your example: 你的例子:

I did something like this: 我做了这样的事情:

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)

In order to eliminate the for statement you made, i did my recursive helper function with 2 accumulators. 为了消除您所做的for语句,我使用2个累加器执行了递归辅助函数。 One to store the results, and one to store the position i'm currently trying. 一种用于存储结果,一种用于存储我当前正在尝试的位置。

Tail Call optimization 尾部呼叫优化

Since no state is being stored on the Non-Border-Cases of the Tail Call stacks, they aren't so important. 由于没有状态存储在尾部调用堆栈的非边界情况下,因此它们并不是那么重要。 Some languages/interpreters then substitute the old stack with the new one. 然后,某些语言/解释器将旧堆栈替换为新堆栈。 So, with no stack frames constraining the number of calls, the Tail Calls behave just like a for-loop . 因此,在没有堆栈帧限制调用次数的情况下, 尾部调用的行为就像一个for循环

But unfortunately for you Python isn't one of these cases. 但是不幸的是,Python并不是其中一种情况。 You'll get a RunTimeError when the stack gets bigger than 1000. Mr. Guido thinks that the clarity lost to debugging purposes due to Tail Call Optimization (caused by the frames thrown awy) is more important than the feature. 当堆栈大于1000时,您将收到RunTimeError。Guido 先生认为,由于尾部调用优化(由抛出的帧引起)而导致调试目的的清晰度比该功能更为重要。 That's a shame. 真可惜 Python has so many cool functional stuff, and tail recursion would be great on top of it :/ Python有很多很棒的功能性东西,并且在它上面尾部递归将是很棒的:/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM