Python 如何将递归转换为尾部递归
是否总是可以将递归转换为尾部递归 我很难将下面的Python函数转换为尾部递归函数Python 如何将递归转换为尾部递归,python,recursion,tail-recursion,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
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
我不确定是否总是这样,但大多数递归函数都可以作为尾部递归实现。此外,尾部递归不同于尾部递归优化 尾部递归和“常规”递归的区别 递归函数中必须存在两个元素:
- 其他返回值
- 函数计算结果
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),依此类推
尾部递归函数将(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)中的框架:
看到区别了吗在“常规”递归调用中,返回函数递归地组成最终值。在尾部递归中,它们只引用基本情况(最后一个已计算)。我们称累加器为跟踪旧值的参数
递归模板
常规递归函数如下所示:
def regular(n)
base_case
computation
return (result of computation) combined with (regular(n towards base case))
要在尾部递归中转换它,我们:
- 引入一个携带累加器的辅助函数
- 在主函数中运行helper函数,将累加器设置为基本情况
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时,将出现运行时错误。
认为由于尾部调用优化(由错误抛出的帧引起)而导致的调试目的的清晰性比特性更重要。真可惜。Python有这么多很酷的功能性东西,而且尾部递归在其上会很棒://您知道Python不会优化尾部递归(一般来说也不会优化尾部调用)?对于某些算法,您还需要在别处存储一些数据。我希望如果我能将其转换为尾部递归,那么将其转换为迭代就很容易了。一般来说,将递归函数转换为尾部递归函数的方法是传递累加器。在这种情况下,唯一的困难是递归调用位于迭代循环中。我认为直接将其转换为迭代会容易得多。这是一个可以接受的答案吗?@abarnert,我想是的。这意味着我必须手动实现回溯。我希望有更聪明的方法。@kirakun:好吧,你可以把迭代循环变成第二级递归,然后把两个函数放在一起,然后把结果函数转换成尾部递归,然后将其转换为迭代……我只是认为这不会比添加手动状态堆栈更容易实现或理解。
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)