【问题标题】:Fibonacci recursion with a stack带有堆栈的斐波那契递归
【发布时间】:2011-03-24 10:40:51
【问题描述】:

我已经向a question 询问过这个问题,但我仍然很困惑。我想将递归函数转换为基于堆栈的函数而无需递归。以斐波那契函数为例:

algorithm Fibonacci(x):
  i = 0
  i += Fibonacci(x-1)
  i += Fibonacci(x-2)
  return i

(是的,我知道我没有设置基本情况,并且斐波那契的递归确实效率低下)

这将如何使用显式堆栈实现?例如,如果我将堆栈作为 while 循环,我必须跳出循环才能评估第一次递归,并且我无法在第一次递归之后返回该行并继续进行第二次递归.

【问题讨论】:

  • 你应该澄清一点。递归也使用堆栈(程序的堆栈),而显式堆栈不是(在我看来)实现该功能的最有效方式。除此之外,语言的堆栈实现差异很大,因此如果您真的在使用哪种语言,那么了解您正在使用的语言会很有用。
  • 斐波那契也许不是最好的例子,因为(至少在概念上)它需要对递归调用的结果进行操作以计算给定帧的结果。如果您实际尝试实现的内容不需要这个,那么使用显式堆栈实现会更简单。
  • 事实上,我想做的事情确实需要这个,这就是为什么我对如何实现它感到困惑。

标签: algorithm


【解决方案1】:

你的问题启发了我写了一段代码,起初这让我很害怕,但我不确定现在该怎么想,所以在这里供你娱乐。也许它可以帮助理解事物。

这是对递归斐波那契函数实现的公然模拟。语言是 C#。对于参数 0,函数返回 0 - 根据 Ronald Graham、Donald Knuth 和 Oren Patashnik 在“具体数学”中给出的斐波那契数列的定义。在维基百科中也是这样定义的。省略了对否定参数的检查。

通常返回地址存储在堆栈中,执行只是跳转到正确的地址。为了模拟这一点,我使用了enum

enum JumpAddress
{
    beforeTheFirstRecursiveInvocation,
    betweenRecursiveInvocations,
    afterTheSecondRecursiveInvocation,
    outsideFibFunction
}

还有一个小状态机。

存储在栈上的Frame是这样定义的:

class Frame
{
    public int argument;
    public int localVariable;
    public JumpAddress returnAddress;
    public Frame(int argument, JumpAddress returnAddress)
    {
        this.argument = argument;
        this.localVariable = 0;
        this.returnAddress = returnAddress;
    }
}

这是一个 C# 类 - 一个引用类型。堆栈包含对放置在堆上的对象的引用,所以当我这样做时:

Frame top = stack.Peek();
top.localVariable = lastresult;

我正在修改堆栈顶部的引用仍然引用的对象,而不是副本。

我通过将帧压入堆栈并将状态机中的执行地址设置为开头 - beforeTheFirstRecursiveInvocation 来模拟函数的调用。

为了从函数返回,我将lastresult 变量、pointOfExecution 变量设置为存储在顶部帧中的返回地址,然后将帧从堆栈中弹出。

这里是代码。

public static int fib(int n)
{
    Stack<Frame> stack = new Stack<Frame>(n);
    //Constructor uses the parameter to reserve space.
    int lastresult = 0; 
    //variable holding the result of the last "recursive" invocation            
    stack.Push(new Frame(n, JumpAddress.outsideFibFunction));
    JumpAddress pointOfExecution = JumpAddress.beforeTheFirstRecursiveInvocation;
    // that's how I model function invocation. I push a frame on the stack and set
    // pointOfExecution. Above the frame stores the argument n and a return address
    // - outsideFibFunction

    while(pointOfExecution != JumpAddress.outsideFibFunction)
    {
        Frame top = stack.Peek();
        switch(pointOfExecution)
        {
            case JumpAddress.beforeTheFirstRecursiveInvocation:
                if(top.argument <= 1)
                {
                    lastresult = top.argument;
                    pointOfExecution = top.returnAddress;
                    stack.Pop();
                }
                else
                {
                    stack.Push(new Frame(top.argument - 1, JumpAddress.betweenRecursiveInvocations));
                    pointOfExecution = JumpAddress.beforeTheFirstRecursiveInvocation;
                }
                break;
            case JumpAddress.betweenRecursiveInvocations:
                top.localVariable = lastresult;
                stack.Push(new Frame(top.argument - 2, JumpAddress.afterTheSecondRecursiveInvocation));
                pointOfExecution = JumpAddress.beforeTheFirstRecursiveInvocation;
                break;
            case JumpAddress.afterTheSecondRecursiveInvocation:
                lastresult += top.localVariable;
                pointOfExecution = top.returnAddress;
                stack.Pop();
                break;
            default:
                System.Diagnostics.Debug.Assert(false,"This point should never be reached");
                break;
        }
    }
    return lastresult;
}

【讨论】:

    【解决方案2】:

    在伪python中

    def fib(x):
      tot = 0
      stack = [x]
      while stack:
         a = stack.pop()
         if a in [0,1]:
            tot += 1
         else:
             stack.push(a - 1)
             stack.push(a - 2)
       return tot
    

    如果您不想要外部计数器,那么您将需要推送跟踪累积和的元组以及这是a - 1 还是a - 2

    可能值得您花时间显式写出调用堆栈(手动,在纸上),以便为您的代码运行说 fib(3)(尽管首先修复您的代码,以便它处理边界条件)。一旦你这样做了,应该很清楚如何在没有堆栈的情况下做到这一点。

    编辑:

    下面我们来分析一下斐波那契算法的运行情况

    def fib(x):
        if (x == 0) or (x == 1):
           return 1
        else:
            temp1 = fib(x - 1)
            temp2 = fib(x - 2)
            return temp1 + temp2
    

    (是的,我知道这甚至不是低效算法的有效实现,我已经声明了不必要的临时变量。)

    现在,当我们使用堆栈进行函数调用时,我们需要在堆栈上存储两种东西。

    1. 返回结果的位置。
    2. 局部变量的空间。

    在我们的例子中,我们有三个可能的返回位置。

    1. 外部来电者
    2. 分配给 temp1
    3. 分配给 temp2

    我们还需要三个局部变量 x、temp1 和 temp2 的空间。让我们检查一下 fib(3)

    当我们最初调用 fib 时,我们告诉堆栈我们想要返回到我们从哪里来的地方,x = 3,并且 temp1 和 temp2 未初始化。

    接下来我们压入我们想要分配 temp1 的堆栈,x = 2 并且 temp1 和 temp2 未初始化。我们再次调用,我们有一堆

    (assign temp1, x = 1, -, -)
    (assign temp1, x = 2, -, -)
    (out         , x = 3, -, -)
    

    我们现在返回 1 并进行第二次调用并获取

    (assign temp2, x = 0, -, -)
    (assign temp1, x = 2, temp1 = 1, -)
    (out         , x = 3, -, -)
    

    现在再次返回 1

    (assign temp1, x = 2, temp1 = 1, temp2 = 1)
    (out         , x = 3, -, -)
    

    所以这会返回 2,我们得到

    (out         , x = 3, temp1 =2, -)
    

    所以我们现在递归到

    (assign temp2, x = 1, -, -)
    (out         , x = 3, temp1 =2, -)
    

    从中我们可以看到我们的出路。

    【讨论】:

      【解决方案3】:
      // 0<x<100
      int fib[100];
      fib[1]=1;
      fib[2]=1;
      if(x<=2)
      cout<<1;
      else{
      for(i=3;i<=x;i++)
         fib[i]=fib[i-1]+fib[i-2];
      cout<<fib[x];
      }
      

      OR 不使用向量

         int x,y,z;
         x=1;y=1;z=1;
         if(x<=2)
          cout<<1;
         else{
         for(i=3;i<=x;i++){
            z=x+y;
            x=y;
            y=z;
      }
      cout<<z;
      }
      

      最后一种方法有效,因为您只需要前 2 个斐波那契数来创建当前的。

      【讨论】:

        【解决方案4】:
        algorithm Fibonacci(x):
          stack = [1,1]
          while stack.length < x
            push to the stack the sum of two topmost stack elements
          return stack.last
        

        您可以将调用之间的堆栈保留为某种缓存。

        这个堆栈不是一个“真正的堆栈”,因为你可以做的不仅仅是推送、弹出和检查它的空性,但我相信这就是你打算做的。

        【讨论】:

          猜你喜欢
          • 2012-05-06
          • 2014-11-14
          • 1970-01-01
          • 2010-12-03
          • 2014-04-02
          • 2012-12-29
          • 2017-11-18
          • 2014-01-08
          相关资源
          最近更新 更多