我听说任何递归算法都可以通过堆栈表达。最近,我一直致力于在可用调用堆栈大小过小的环境中开发程序。
我需要进行一些深度递归,所以我想知道如何重写任何递归算法以使用显式堆栈。
例如,假设我有一个像这样的递归函数
function f(n, i) {
if n <= i return n
if n % i = 0 return f(n / i, i)
return f(n, i + 1)
}
我怎么能用堆栈来写呢?是否有一个简单的过程可以将任何递归函数转换为基于堆栈的函数?
答案 0 :(得分:8)
如果您了解函数调用如何影响进程堆栈,您可以自己了解如何执行此操作。
调用函数时,会在堆栈中写入一些数据,包括参数。该函数读取这些参数,对它们执行任何操作并将结果放在堆栈上。你可以做同样的事情。你的例子特别不需要堆栈,所以如果我将它转换为使用堆栈的那个,它可能看起来有点傻,所以我将给你斐波那契的例子:
fib(n)
if n < 2 return n
return fib(n-1) + fib(n-2)
function fib(n, i)
stack.empty()
stack.push(<is_arg, n>)
while (!stack.size() > 2 || stack.top().is_arg)
<isarg, argn> = stack.pop()
if (isarg)
if (argn < 2)
stack.push(<is_result, argn>)
else
stack.push(<is_arg, argn-1>)
stack.push(<is_arg, argn-2>)
else
<isarg_prev, argn_prev> = stack.pop()
if (isarg_prev)
stack.push(<is_result, argn>)
stack.push(<is_arg, argn_prev>)
else
stack.push(<is_result, argn+argn_prev>)
return stack.top().argn
说明:每次从堆栈中取出一个项目时,都需要检查是否需要扩展它。如果是这样,在堆栈上推送适当的参数,如果没有,让它与之前的结果合并。在fibonacci的情况下,一旦fib(n-2)
被计算(并且在堆栈顶部可用),就会检索n-1
(一个在堆栈顶部之后),fib(n-2)
的结果被推到它下面,然后fib(n-1)
被扩展和计算。如果堆栈的前两个元素都是结果,当然,你只需添加它们并推送到堆栈。
如果你想看看你自己的功能是什么样的,那么它就是:
function f(n, i)
stack.empty()
stack.push(n)
stack.push(i)
while (!stack.is_empty())
argi = stack.pop()
argn = stack.pop()
if argn <= argi
result = argn
else if n % i = 0
stack.push(n / i)
stack.push(i)
else
stack.push(n)
stack.push(i + 1)
return result
答案 1 :(得分:3)
您的特定示例是尾递归的,因此使用适当优化的编译器,它根本不应消耗任何堆栈深度,因为它等同于简单的循环。需要说明的是:此示例根本不需要堆栈。
答案 2 :(得分:3)
您可以将代码转换为使用如下堆栈:
stack.push(n)
stack.push(i)
while(stack.notEmpty)
i = stack.pop()
n = stack.pop()
if (n <= i) {
return n
} else if (n % i = 0) {
stack.push(n / i)
stack.push(i)
} else {
stack.push(n)
stack.push(i+1)
}
}
注意:我没有对此进行测试,因此它可能包含错误,但它会为您提供想法。
答案 3 :(得分:1)
你的例子和斐波纳契函数都可以迭代重写而不使用堆栈。
以下是需要堆栈的示例Ackermann function:
def ack(m, n):
assert m >= 0 and n >= 0
if m == 0: return n + 1
if n == 0: return ack(m - 1, 1)
return ack(m - 1, ack(m, n - 1))
消除递归:
def ack_iter(m, n):
stack = []
push = stack.append
pop = stack.pop
RETURN_VALUE, CALL_FUNCTION, NESTED = -1, -2, -3
push(m) # push function arguments
push(n)
push(CALL_FUNCTION) # push address
while stack: # not empty
address = pop()
if address is CALL_FUNCTION:
n = pop() # pop function arguments
m = pop()
if m == 0: # return n + 1
push(n+1) # push returned value
push(RETURN_VALUE)
elif n == 0: # return ack(m - 1, 1)
push(m-1)
push(1)
push(CALL_FUNCTION)
else: # begin: return ack(m - 1, ack(m, n - 1))
push(m-1) # save local value
push(NESTED) # save address to return
push(m)
push(n-1)
push(CALL_FUNCTION)
elif address is NESTED: # end: return ack(m - 1, ack(m, n - 1))
# old (m - 1) is already on the stack
push(value) # use returned value from the most recent call
push(CALL_FUNCTION)
elif address is RETURN_VALUE:
value = pop() # pop returned value
else:
assert 0, (address, stack)
return value
请注意,此处不必将CALL_FUNCTION
,RETURN_VALUE
标签和value
放在堆栈上。
print(ack(2, 4)) # -> 11
print(ack_iter(2, 4))
assert all(ack(m, n) == ack_iter(m, n) for m in range(4) for n in range(6))
print(ack_iter(3, 4)) # -> 125