我已经问过a question这件事,但我仍然感到困惑。我想将递归函数转换为基于堆栈的函数而不递归。以斐波纳契函数为例:
algorithm Fibonacci(x):
i = 0
i += Fibonacci(x-1)
i += Fibonacci(x-2)
return i
(是的,我知道我没有提出一个基本案例,斐波那契的递归效率非常低)
如何实现使用显式堆栈?例如,如果我将堆栈作为while循环,我必须跳出循环以评估第一次递归,并且我无法在第一次递归后返回到该行并继续第二次递归
答案 0 :(得分:4)
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
。
可能值得你花时间为你的代码显式地写出调用堆栈(手工,在纸上)运行say fib(3)(尽管先修复你的代码以便处理边界条件)。一旦你这样做,应该清楚如何没有堆栈。
修改强>
让我们分析以下Fibonacci算法的运行
def fib(x):
if (x == 0) or (x == 1):
return 1
else:
temp1 = fib(x - 1)
temp2 = fib(x - 2)
return temp1 + 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, -)
我们可以从中看到出路。
答案 1 :(得分:2)
你的问题激励我写一段代码,最初吓到了我,但我现在还不确定该怎么想,所以这里是为了你的娱乐。也许它可以帮助一点,理解事物。
这是对递归Fibonacci函数实现的执行的公然模拟。语言是C#。对于参数0,该函数返回0 - 根据Ronald Graham,Donald Knuth和Oren Patashnik在“混凝土数学”中给出的Fibonacci序列的定义。它也是在维基百科中以这种方式定义的。省略了对负参数的检查。
通常,返回地址存储在堆栈中,执行只会跳转到正确的地址。为了模拟这个,我使用了enum
enum JumpAddress
{
beforeTheFirstRecursiveInvocation,
betweenRecursiveInvocations,
afterTheSecondRecursiveInvocation,
outsideFibFunction
}
和一个小型的状态机。
存储在堆栈中的帧定义如下:
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 :(得分:1)
algorithm Fibonacci(x):
stack = [1,1]
while stack.length < x
push to the stack the sum of two topmost stack elements
return stack.last
您可以将调用之间的堆栈保留为某种缓存。
这个堆栈不是一个“真正的堆栈”,因为你不仅可以推送,弹出和检查它的空白,但我相信这是你打算做的。
答案 3 :(得分:1)
// 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];
}
或不使用矢量
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个斐波纳契数来创建当前的数字。