递归的替代方法

时间:2017-12-20 01:10:45

标签: c++

我编写了一个C ++计算器,可以解释函数定义和正常表达式。在计算递归函数时,它的工作方式如下,

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

首先它存储f的定义,当被要求f(400)时,它会在定义中用x代替400,并计算出来。

这适用于400甚至4000,但显然它是递归的,所以如果数量足够大,堆栈最终会用完。

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

p.s.以前存储计算值。

4 个答案:

答案 0 :(得分:1)

这是Memoization的教科书用例。首先,请注意f(x)需要f(x-1),这意味着它还需要f(x-1-1),这与f(x)也需要f(x-2)相同。因此,您可以通过存储"缓存"来节省大量冗余计算。之前的计算并在递归前查看。

答案 1 :(得分:1)

如果正在实现具有足够功率的语言,则以该语言执行某些程序将需要任意数量的堆栈空间。 Memoization(或动态编程)可以针对特定情况优化堆栈空间量,但它们不能限制一般使用的堆栈。类似地,对于尾递归,因为并非所有函数都是尾递归的。 (虽然可以将递归转换为迭代加上堆栈的方式相同,但可以将程序转换为连续传递形式,可以考虑尾递归的推广。)

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

所有这一切,只是使用实现语言的调用堆栈是很常见的,因为它更容易,并提供舒适的机制经济。可以在大多数操作系统中增加堆栈限制并将其设置为例如对于希望处理的所有实际问题,16MB可能就足够了。参见例如UNIX上的shell中的limit命令,如系统。

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

答案 2 :(得分:0)

好吧,既然问题被标记为,为什么不考虑std::stack?这可以避免使用程序的调用堆栈。

std::stack<myfunction> mycallstack;
mycallstack.push( /* first function */ );
while (!mycallstack.empty()) {
    /* function */ = mycallstack.pop();
    if ( /* whatever */ ){
        mycallstack.push( /* recursing function call */ );
    }
}

你甚至可以将它与我不太熟悉的std::tuple混合使用。

答案 3 :(得分:0)

你可以尝试用“反向抛光表示法”转换整个表达式。 所以你可以避免所有的递归调用。

对您的表达进行标记并以RPN顺序带来必要的标记 - &gt;在简单的for循环中以O(n)计算。

https://en.wikipedia.org/wiki/Reverse_Polish_notation

转型的想法:

您分析递归表达式然后转换它 中缀符号。对于简单场景来说,这似乎并不难。 这种转换(无计算)必须以非递归方式完成。

如果您将其转换为中缀表示法,则可以轻松将其转换为 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))