递归vs循环

zil*_*n01 48 recursion loops

我面临一个问题,即递归和使用循环似乎都是自然的解决方案.对于像这样的案件,是否有惯例或"首选方法"?(显然它不像下面那么简单)

递归

Item Search(string desired, Scope scope) {
    foreach(Item item in scope.items)
        if(item.name == desired)
            return item;

    return scope.Parent ? Search(desired, scope.Parent) : null;
}
Run Code Online (Sandbox Code Playgroud)

Item Search(string desired, Scope scope) {
    for(Scope cur = scope; cur != null; cur = cur.Parent)
        foreach(Item item in cur.items)
            if(item.name == desired)
                return item;

    return null;
}
Run Code Online (Sandbox Code Playgroud)

Joh*_*lla 56

我赞成递归解决方案:

  • 递归的实现比迭代解决方案简单得多,通常是因为它以迭代方法不能的方式利用问题的结构方面

  • 我可以合理地确定递归的深度不会导致堆栈溢出,假设我们正在谈论以这种方式实现递归的语言

条件1似乎不是这里的情况.迭代解决方案的复杂程度大致相同,因此我坚持使用迭代路由.

  • 也许在堆栈深度是一个问题的情况下添加一个关于尾递归的附加点(我知道它不适用于 Java)? (2认同)

RBe*_*eig 28

如果绩效很重要,那么对两者进行基准测试并在合理的基础上进 如果没有,则根据复杂性进行选择,并考虑可能的堆栈溢出.

经典着作"编程风格的元素"(由Kernighan和Plauger 撰写)有一个指南,即算法应遵循数据结构.也就是说,递归结构通常使用递归算法更清晰地处理.


Tyl*_*nry 19

递归用于表示以更容易理解的形式自然递归的算法."自然递归"算法是这样一种算法,其中答案是根据较小子问题的答案构建的,这些子问题又是根据较小的子问题的答案等构建的.例如,计算因子.

在一种不起作用的编程语言中,迭代方法几乎总是比递归方法更快,更有效,因此使用递归的原因是清晰度,而不是速度.如果递归实现最终不如迭代实现那么清晰,那么一定要避免它.

在这种特殊情况下,我会判断迭代实现更清晰.

  • 在一个循环中,每次迭代都是一个'刷新'范围,所有来自前一次迭代的变量都被删除(迭代i-1的变量不能从迭代i访问,因为来自i-1的变量被删除了从迭代i-1转换到迭代i).在递归函数中,每个新迭代在最后一次迭代的子范围内运行(迭代i-1的变量在迭代1期间仍然存在,它们只存在于不同的范围内).这描述了过程语言,但是一些函数式语言实际上进行了优化,使得递归更有效 (3认同)

And*_*per 13

好吧,我看到了大量的答案,甚至接受了答案,但从未看到正确的答案,并在想为什么......

\n\n

长话短说 :

\n\n

如果您可以通过循环生成相同的单元,请始终避免递归!

\n\n

递归是如何工作的?

\n\n

\xe2\x80\xa2 堆栈内存中的帧被分配给单个函数调用

\n\n

\xe2\x80\xa2 框架包含对实际方法的引用

\n\n

\xe2\x80\xa2 如果方法有对象,则对象将被放入堆内存中,并且 Frame 将包含对堆内存中该对象的引用。

\n\n

\xe2\x80\xa2 这些步骤是为每个方法调用完成的!

\n\n

风险:

\n\n

\xe2\x80\xa2 当堆栈没有内存来放置新的递归方法时,StackOverFlow。

\n\n

\xe2\x80\xa2 OutOfMemory 当堆没有内存来放置递归存储对象时。

\n\n

循环如何工作?

\n\n

\xe2\x80\xa2 之前的所有步骤,除了循环内重复执行代码将不会消耗任何进一步的数据(如果已消耗)。

\n\n

风险:

\n\n

\xe2\x80\xa2 当你的条件永远不会存在时,单一风险存在于 while 循环内...\n好吧,这不会导致任何崩溃或其他任何事情,如果你天真地这样做,它只是不会退出循环while(true): )

\n\n

测试:

\n\n

在您的软件中执行下一步:

\n\n
private Integer someFunction(){\n\nreturn someFunction();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

StackOverFlow很快就会得到例外,也许OutOfMemory也是

\n\n

做第二件事:

\n\n
while(true){\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

软件只会冻结,不会发生崩溃:

\n\n

最后但并非最不重要的 - for循环:

\n\n

总是使用for循环,因为这样或那样的循环会迫使你给出一个断点,超过这个点循环就不会继续,当然你可能会非常生气,然后找到一种方法让循环永远不会for停止,但我建议你这样做为了内存管理和提高软件的生产力,始终使用循环而不是递归,这是当今的一个大问题。

\n\n

参考:

\n\n

基于堆栈的内存分配

\n


Ed *_* S. 9

如果您使用的是函数式语言(似乎不是这样),请使用递归.如果没有,那么在项目中工作的其他人可能会更好地理解循环.当然,某些任务(如递归搜索目录)比其他任务更适合递归.

此外,如果代码无法针对尾端递归进行优化,则循环更安全.


Emi*_*l H 7

使用循环.它更容易阅读和理解(阅读代码总是比编写代码困难得多),并且通常要快得多.


Not*_*ure 5

可以证明,所有尾递归算法都可以展开成一个循环,反之亦然。一般来说,递归算法的递归实现比循环实现更容易让程序员遵循,也更容易调试。同样一般来说,循环实现的实际性能会更快,因为循环中的分支/跳转通常比推送和弹出堆栈帧执行得更快。

就个人而言,对于尾递归算法,除了性能最密集的情况外,我更喜欢在所有情况下坚持使用递归实现。