我喜欢递归。我认为它简化了很多事情。另一个可能不同意;我认为它也使代码更容易阅读。但是,我注意到递归在C#等语言中并没有像在LISP中那样被使用(顺便说一下,由于递归,它是我最喜欢的语言)。
有没有人知道在C#等语言中是否有任何好的理由不使用递归?它比迭代更昂贵吗?
答案 0 :(得分:18)
它们比它贵吗? 迭代?
是的。许多Lisp变体支持“尾调用优化”的思想,它允许将递归函数调用的许多用法转换为迭代函数(这简化了一点)。如果不支持tail-call,那么递归函数调用将逐渐使用堆栈中的更多内存。
答案 1 :(得分:15)
它们比它贵吗? 迭代?
是的。递归需要创建一个新的堆栈帧以及调用和返回,而迭代通常只需要一个比较和分支,使其大大加快;但是,编译器可以对某些递归调用(即尾调用)执行尾调用优化,这允许它们重用堆栈帧,使递归调用更便宜并有效地将它们转换为迭代。 Scheme实际上要求方案编译器实现尾调用优化。
答案 2 :(得分:11)
现代CPU的编译器和体系结构可以通过迭代进行大量优化,而这些优化不能用于递归。例如,处理器执行乐观计算的能力。当处理器找到迭代空间时,它知道需要多长时间。从一开始,就没有必要在开始下一个循环之前每次检查。因此,他们管理运营。由于大多数CPU可以同时执行多个操作(通过流水线操作),因此可以在比递归更短的时间内解决迭代空间。这甚至是尾部优化,因为CPU实际上一次处理大约4个循环(对于x4性能增益)。此增益取决于CPU的体系结构。该体系结构可能是增益的重要组成部分,下一代CPU将进一步推动增益。
递归的真正问题在于我们的硬件是基于VonNeumann的(图灵机的可实现的变体),而不是Lisp机器,虽然有一些专门的Lisp硬件,但你找不到任何带有它的桌面:)< / p>
答案 3 :(得分:10)
Lisp和F#等函数语言可以在内部实现许多尾递归函数作为循环,并且能够避免函数调用的堆栈开销。 C#不支持尾递归,尽管F#没有。
答案 4 :(得分:7)
使用递归有很多优点和缺点。绝对代码简单性是最大的,它也有助于提高代码的可维护性和减少错误。
递归的最大危险是边缘情况,算法旋转失控以打破函数堆栈限制。某些语言,Progress的ABL语言为1,具有允许的最高嵌套调用级别的参数。这些通常很低,并且向混合中添加递归可能会使应用程序中断该限制。
简而言之,应该始终使用紧密终止案例来实现递归,否则调试非常困难(因为不可预测性),并且可能会在生产代码中引起严重问题。
对于内存和速度的关注,除非这是一个方法本身很短(时间明智)被多次调用,所以性能是什么并不重要。
示例:如果使用递归扫描硬盘驱动器的所有文件和文件夹,则递归所带来的性能影响是达到命中硬盘驱动器并获取文件系统信息所需的时间。在这种情况下,递归可能优于迭代处理。
另一个例子:如果你扫描树结构的节点,迭代过程可能更有利,因为我们没有涉及功能堆栈,这意味着我们打算减少内存并可能让硬件使用更多的缓存。有关详细信息,请参阅Robert Gould的回复。
答案 5 :(得分:6)
如果算法最能以递归形式表达,和如果堆栈深度很小(在log(N)范围内,即通常<20),那么无论如何使用递归。 (由迭代引起的任何性能提升都是一个很小的常数因素。)
如果存在堆栈变大的危险,并且如果您的语言/编译器/运行时系统不能保证尾调用优化,则应避免使用递归算法。
答案 6 :(得分:3)
有没有人知道在C#等语言中是否有任何好的理由不使用递归?它比迭代更昂贵吗?
是的,因为C#不支持tail recursion。当通过大型集合使用递归进行简单迭代时,如果递归得太深,就可以轻松获得StackOverflowException并使应用程序崩溃。
答案 7 :(得分:2)
选择不仅仅取决于要解决的问题,还取决于所使用的语言。在C风格的语言(Java,C#,或者你有什么)中,我的下意识反应是避免递归,因为它看起来对我来说完全陌生(我只是不能认为递归),更不用说堆栈滥用的可能性了。但是,有些问题使用 other 而不是递归几乎没有意义 - 树遍历是一个很好的例子。迭代解决方案是完全合理的,但代码会更大,更笨拙,而且几乎肯定不太可读。
但是,更多动态语言(如Lisp或Python)会花费很多时间来使递归更自然。我的个人反应是首先寻找一个迭代的解决方案,无论出现什么问题,但不同的里程数会使赛马发生。
最后,最好的选择可能只是写它。在任何情况下你都会把它扔掉一次很好。
答案 8 :(得分:1)
如果问题可以减少到迭代,那么我会迭代。
如果问题需要递归(例如树导航),那么我会递归。
据说我主要在C#中创建业务线应用程序 - 我确信科学编程有不同的要求。
答案 9 :(得分:1)
Scheme是我所知道的唯一需要尾调用优化的Lisp方言,它们倾向于使用递归。在其他不需要这个的Lisp方言中(比如Common Lisp),我没有看到任何其他语言使用的递归。
答案 10 :(得分:1)
递归是(不可能/更难)并行化而不是迭代
现代cpus有多核,因此如果你为递归设计,使用parallel.for进行直接优化(以及此类技术)会变得更加困难。
然而,并行化仍然相当模糊,而且很少有人使用它。
此外,我认为递归算法更易于设计和思考,因为它们同时涉及的代码和变量略少。当没有表现必要时,我通常会去递归。
答案 11 :(得分:0)
我认为递归函数必须放在堆栈上 - 每次函数调用自身时,该函数的另一个副本都会进入函数堆栈,依此类推。问题在于,在某些时候,堆栈空间不足以存储函数,因此如果数字变大,则递归解决方案可能无效。我知道因为它让我陷入困境 - 我对free()
我的整个链表有一个很好的递归函数,然后我用最大的链表进行了测试,我得到了一个错误(我相信这是一个段错误 - 绝对应该是堆栈溢出:))。
迭代函数没有这些错误 - 在机器语言中,它们被表达为简单的'jmp'或'je'等等,因此它们永远不会在功能堆栈上耗尽空间,并且实际上更加清晰。
这是一个完全主观的问题,但如果不是因为递归对你的计算机有内置限制这一事实,我会说这是一个更好的解决方案。对问题的一些迭代解决方案看起来很难看,而递归解决方案看起来更清晰,更好。但这就是生活。
答案 12 :(得分:0)
当处理诸如列表或向量之类的固有线性数据结构时,优先考虑迭代构造而不是递归的一个原因是它更好地传达了代码的意图。通常,当迭代时不加选择地使用递归时,读者需要花费更多精力来辨别程序的结构。
答案 13 :(得分:0)
我只是抛弃那里的XSLT变量在创建后是只读的。因此,可以通过为循环编制索引来完成,例如
for(int i=0; i < 3; i++) doIt(i);
实际上是通过递归来完成的。相当于
的东西public rediculous(i) {
doIt(i);
if (i < 3) rediculous(i + 1);
}
我实际上会在这里提供一个XSLT示例,但所有打字都会让Baby Jesus哭泣。