如何使用堆栈模拟递归?

时间:2012-03-22 22:45:31

标签: language-agnostic recursion

我听说任何递归算法都可以通过堆栈表达。最近,我一直致力于在可用调用堆栈大小过小的环境中开发程序。

我需要进行一些深度递归,所以我想知道如何重写任何递归算法以使用显式堆栈。

例如,假设我有一个像这样的递归函数

function f(n, i) {
  if n <= i return n
  if n % i = 0 return f(n / i, i)
  return f(n, i + 1)
}

我怎么能用堆栈来写呢?是否有一个简单的过程可以将任何递归函数转换为基于堆栈的函数?

4 个答案:

答案 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_FUNCTIONRETURN_VALUE标签和value放在堆栈上。

Example

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