递归的替代方法 我编写了一个C++计算器,它可以解释函数定义以及正常表达式。当计算递归函数时,它是这样工作的

递归的替代方法 我编写了一个C++计算器,它可以解释函数定义以及正常表达式。当计算递归函数时,它是这样工作的,c++,C++,例如: 输入:f(1)=1;f(2)=1;f(x)=f(x-1)+f(x-2);f(400) 首先,它存储f的定义,当要求f(400)时,它用400代替定义中的x,并计算该值 这对400甚至4000都适用,但显然它是递归的,因此如果数字足够大,堆栈最终将被用完 我想知道是否有办法改进算法并避免这个问题? (我考虑过创建新的线程或进程,或者进行潜水计算,因此当要求10000时,它首先计算2000,然后计算4000,…,最后计算10000,但这似乎不太方便。) p、 存储以前计算的值。这是一个教科书

例如: 输入:
f(1)=1;f(2)=1;f(x)=f(x-1)+f(x-2);f(400)

首先,它存储
f
的定义,当要求
f(400)
时,它用400代替定义中的
x
,并计算该值

这对400甚至4000都适用,但显然它是递归的,因此如果数字足够大,堆栈最终将被用完

我想知道是否有办法改进算法并避免这个问题? (我考虑过创建新的线程或进程,或者进行潜水计算,因此当要求10000时,它首先计算2000,然后计算4000,…,最后计算10000,但这似乎不太方便。)


p、 存储以前计算的值。

这是一个教科书上的使用案例。首先,注意f(x)需要f(x-1),这意味着它也需要f(x-1-1),这与f(x)也需要的f(x-2)相同。因此,你可以通过存储以前计算中的“缓存”并在递归之前查看它来节省大量的冗余计算。这可以很好地避免使用程序的调用堆栈

std::stack<myfunction> mycallstack;
mycallstack.push( /* first function */ );
while (!mycallstack.empty()) {
    /* function */ = mycallstack.pop();
    if ( /* whatever */ ){
        mycallstack.push( /* recursing function call */ );
    }
}
std::stack mycallstack;
push(/*第一个函数*/);
而(!mycallstack.empty()){
/*函数*/=mycallstack.pop();
如果(/*无论什么*/){
push(/*递归函数调用*/);
}
}

您甚至可以将其与我不太熟悉的语言混合使用。

如果要实现一种具有足够能力的语言,则使用该语言执行某些程序将需要任意数量的堆栈空间。记忆(或动态编程)可以优化特定情况下的堆栈空间量,但不能绑定通常使用的堆栈。与尾部递归类似,并非所有函数都是尾部递归的。(虽然以同样的方式,可以将递归转换为迭代加栈,但是可以将程序转换成连续传递形式,这可以考虑尾部递归的泛化。)

因此,如果为例如下推自动机实现解释器,并且希望处理大型问题,则通常将堆栈作为解释器中的内部数据结构来实现。有几种方法可以改善情况。首先,堆栈上的条目可以小于函数调用堆栈帧。第二,堆栈可以实现为一个链表(一个“线程堆栈”,尽管它与并发编程中作为执行单元的“线程堆栈”的含义完全不同)。第三,当堆栈耗尽空间时,可以以非常干净的方式处理它,这在大多数编译编程语言中很难处理调用堆栈耗尽的问题。(即使在某些环境中识别堆栈溢出错误也有点乏味。尽早学习有界递归设计是一项很好的技能。在高可靠性或高安全性应用中更是如此。递归算法通常在低级别嵌入式系统中完全避免。)

尽管如此,仅仅使用实现语言的调用堆栈是非常常见的,因为它更简单,并且提供了一种舒适的经济机制。可以增加大多数操作系统中的堆栈限制,并将其设置为16MB可能足以解决所有希望处理的实际问题。例如,请参见类UNIX系统上shell中的
limit
命令


除此之外,特定语言的高质量实现将使用诸如记忆、尾部递归、符号简化等技术,以大大减少给定程序使用的资源。但是仍然会有一些程序拒绝这种优化。

您可以尝试用“反向波兰符号”转换整个表达式。 因此可以避免所有递归调用

标记化您的表达式,并在一个简单的for循环中按照RPN顺序->O(n)中计算所需的标记

转型理念:

分析递归表达式,然后对其进行转换 中缀符号。对于简单的场景来说似乎并不难。 此转换(无计算)必须以非递归方式进行

如果将其转换为中缀符号,则可以轻松地将其转换为 RPN并快速求解,无需递归

**n! - function - example**

#recursive expression
f(1)=1
f(n)=f(n−1) * n

#build substitution-lookups (example for n=4)
f(4)       = f(4−1) * 4
f(4−1)     = f(4−1-1) * (4-1)
f(4−1-1)   = f(4−1-1-1) * (4−1-1)
f(4−1-1-1) = 1

#build infix-notation with help of the lookups
f(4−1) * 4
(f(4−1-1) * (4-1)) * 4
((f(4−1-1-1) * (4−1-1)) * (4-1)) * 4
(((1) * (4−1-1)) * (4-1)) * 4
((1 * (4−1-1)) * (4-1)) * 4

**fibonacci - function - example (simplified) **

#recursive expression
f(n) = f(n-1) + f(n-2)
f(1) = 1
f(2) = 1

#modify it a little bit
f(n) = f(n-1) + f(n-1-1)
f(1) = 1
f(2) = 1

#build substitution-lookups (example for n=6)
f(6)          = f(6-1) + f(6-1-1)
f(6-1)        = f(6-1-1) + f(6-1-1-1)
f(6-1-1)      = f(6-1-1-1) + f(6-1-1-1-1)
f(6-1-1-1)    = f(6-1-1-1-1) + f(6-1-1-1-1-1)
f(6-1-1-1-1)  = 1
f(6-1-1-1-1-1)= 1

#build infix-notation with help of the lookups
f(6-1) + f(6-1-1)
(f(6-1-1) + f(6-1-1-1)) + (f(6-1-1-1) + f(6-1-1-1-1))
((f(6-1-1-1) + f(6-1-1-1-1)) + (f(6-1-1-1-1) + f(6-1-1-1-1-1))) + ((f(6-1-1-1-1) + f(6-1-1-1-1-1)) + (1))
(((f(6-1-1-1-1) + f(6-1-1-1-1-1)) + 1) + (1 + 1)) + ((1 + 1) + (1))
(((1 + 1) + 1) + (1 + 1)) + ((1 + 1) + (1))

了解尾部调用优化。此外,尽量记住以前的结果以删除多余的呼叫。1。递归总是可以转换为迭代+维护自己的堆栈(对于相同的问题,这通常比调用堆栈占用更少的空间)。2.如果您密切关注这个问题,您甚至不需要堆栈,因为计算
f(n-2)
涉及计算
f(n-1)
。不确定计算是否可以并行进行。f(n)依赖于f(n-1)。计算应按顺序进行。问题不仅在于堆栈的大小。请注意,例如,f(100)被调用/计算两次:一次用于f(101),另一次用于f(102)。这就是为什么这个特殊问题的递归解会很慢的原因。注意,对于这个特殊的问题,你可以在恒定的时间和恒定的内存中进行,因为第n个斐波那契数有一个封闭形式的解。但这并不能真正解决计算器中递归的问题。@Stephen很抱歉,我忘了提到我使用过这种技术。除了记忆本身并不能真正解决堆栈空间不足的问题。@ZalmanStern:不是显式和直接的,但一旦你有了记忆,堆栈问题就很容易解决了(如果它仍然存在,这可能不是因为记忆本身实际上减少了递归调用的数量)。@JohnZwinck,同时它减少了num