通过一般的转换到迭代来解决这个问题是一个坏主意。但是,这是你问的。
这些都不是解决fib 的好方法:fib 有封闭形式的解决方案,和/或更清洁的迭代解决方案,和/或递归记忆解决方案。相反,我展示了相对机械技术来获取递归函数(不是尾递归或其他简单的解决方法),并在不使用自动存储堆栈(递归)的情况下解决它。
我的代码递归嵌套太深,在中高复杂度的情况下会破坏堆栈;当重构为迭代时,问题就消失了。当您拥有的是一个您一半理解的递归解决方案并且您需要它是迭代的时,这些是所需的解决方案。
将递归转换为迭代解决方案的一般方法是手动管理堆栈。
在这种情况下,我还将记住返回值。
我们将返回值缓存在retvals中。
如果我们不能立即解决问题,我们说明我们首先需要解决哪些问题才能解决我们的问题(特别是 n-1 和 n-2 的情况)。然后我们再次排队解决我们的问题(到那时,我们将准备好我们需要的东西)。
int fib( int n ) {
std::map< int, int > retvals {
{1,3},
{2,2}
};
std::vector<int> arg;
arg.push_back(n);
while( !arg.empty() ) {
int n = arg.back();
arg.pop_back();
// have we solved this already? If so, stop.
if (retvals.count(n)>0)
continue;
// are we done? If so, calculate the result:
if (retvals.count(n-1)>0 && retvals.count(n-2)>0) {
retvals[n] = retvals[n-1] + retvals[n-2];
continue;
}
// to calculate n, first calculate n-1 and n-2:
arg.push_back(n); arg.push_back(n-1); arg.push_back(n-2);
}
return retvals[n];
}
没有递归,只是一个循环。
执行此操作的“愚蠢”方法是获取函数并将其设为伪协程。
首先,重写你的递归代码,每行做一件事:
int fib(int n) {
if(n == 1)
return 3
if (n == 2)
return 2
int a = fib(n-2);
int b = fib(n-1);
return a+b;
}
接下来,创建一个包含所有函数状态的结构体:
struct fib_data {
int n, a, b, r;
};
并在我们进行递归调用的每个点添加标签,以及具有相似名称的枚举:
enum Calls {
e1, e2
};
int fib(int n) {
fib_data d;
d.n = n;
if(d.n == 1)
return 3
if (d.n == 2)
return 2
d.a = fib(n-2);
CALL1:
d.b = fib(n-1);
CALL2:
d.r = d.a+d.b;
return d.r;
}
将CALLS 添加到您的fib_data。
接下来创建fib_data的堆栈:
enum Calls {
e0, e1, e2
};
struct fib_data {
Calls loc = Calls::e0;
int n, a, b, r;
};
int fib(int n) {
std::vector<fib_data> stack;
stack.push_back({n});
if(stack.back().n == 1)
return 3
if (stack.back().n == 2)
return 2
stack.back().a = fib(stack.back().n-2);
CALL1:
stack.back().b = fib(stack.back().n-1);
CALL2:
stack.back().r = stack.back().a + stack.back().b;
return stack.back().r;
}
现在创建一个循环。不要递归调用,而是在您的fib_data 中设置返回位置,将fib_data 与n 和e0 位置一起压入堆栈,然后继续循环。在循环的顶部,切换到栈顶的位置。
返回:创建一个函数局部变量 r 来存储返回值。要返回,请设置r,弹出堆栈,然后继续循环。
如果堆栈在循环开始时为空,则从函数返回r。
enum Calls {
e0, e1, e2
};
struct fib_data {
int n, a, b, r;
Calls loc = Calls::e0;
};
int fib(int n) {
std::vector<fib_data> stack;
stack.push_back({n});
int r;
while (!stack.empty()) {
switch(stack.back().loc) {
case e0: break;
case e1: goto CALL1;
case e2: goto CALL2;
};
if(stack.back().n == 1) {
r = 3;
stack.pop_back();
continue;
}
if (stack.back().n == 2){
r = 2;
stack.pop_back();
continue;
}
stack.back().loc = e1;
stack.push_back({stack.back().n-2});
continue;
CALL1:
stack.back().a = r;
stack.back().loc = e2;
stack.push_back({stack.back().n-1});
continue;
CALL2:
stack.back().b = r;
stack.back().r = stack.back().a + stack.back().b;
r = stack.back().r;
stack.pop_back();
continue;
}
}
然后请注意 b 和 r 不必在堆栈中 -- 将其删除,并使其成为本地。
这种“愚蠢”转换模拟 C++ 编译器在递归时所做的事情,但堆栈存储在空闲存储区而不是自动存储区,并且可以重新分配。
如果指向局部变量的指针需要保持不变,则对堆栈使用std::vector 将不起作用。将带有偏移量的指针替换为标准向量,就可以了。