【发布时间】:2014-07-06 02:57:50
【问题描述】:
我看到了this 视频,该视频正在讨论如何使用 for 循环编写大多数递归函数,但是当我想到它时,我看不出两者之间的逻辑差异。我在这里找到了this 主题,但它只关注实际差异,就像网络上许多其他类似主题一样,那么处理循环和递归的方式有什么逻辑差异?
【问题讨论】:
我看到了this 视频,该视频正在讨论如何使用 for 循环编写大多数递归函数,但是当我想到它时,我看不出两者之间的逻辑差异。我在这里找到了this 主题,但它只关注实际差异,就像网络上许多其他类似主题一样,那么处理循环和递归的方式有什么逻辑差异?
【问题讨论】:
前面的底线——递归更通用,但在实践中通常比循环效率低。
如果您愿意,原则上循环始终可以实现为递归。实际上,堆栈资源的限制严重限制了您可以解决的问题的大小。我可以并且已经构建了迭代十亿次的循环,除非我确定编译器可以并且会将递归转换为循环,否则我永远不会尝试使用递归。由于堆栈限制和效率,人们经常尝试为递归找到循环等效项。
尾递归总是可以转换为循环。但是,存在无法转换的递归。例如,我从事实验的统计设计工作。有时,大型设计是通过“交叉”几个较小的子设计来构建的。交叉是您将第二个设计的每一行连接到第一个设计的每一行的地方。对于两个子设计,所有这些需要都是简单的嵌套循环,但对于三个或更多设计,您需要增加嵌套级别,为每个额外的子设计添加一个嵌套级别。因此,虽然原则上这是嵌套循环,但实际上嵌套的数量是可变的。如果您尝试使用循环来实现它,则每次处理要交叉的不同数量的子设计时都必须修改程序以添加/减去嵌套循环,因此您无法编写不可变的基于循环的版本。这可以通过递归轻松实现。在这种情况下,我很乐意牺牲一点效率,因为我在 6 年前编写和调试了代码,并且从那时起就不必修改它,尽管从那时起创建了许多不同复杂度的交叉设计。
【讨论】:
思考这个问题的一种方法是,选择递归还是迭代取决于您对要解决的问题的看法。某些“思维方式”更自然地导致递归解决方案,而其他思维方式导致更多迭代解决方案。对于任何问题,您原则上都可以以一种为您提供递归解决方案或一种为您提供迭代解决方案的方式进行思考。 (有时迭代解决方案最终会模拟一个递归堆栈,但那里没有实际的递归。)
这是一个例子。您有一个整数数组(正数或负数),并且您想要找到最大段总和。段是连续的数组的一部分。所以在数组 [3, -4, 2, 1, -2, 4] 中,最大段总和是 5,你可以从段 [2, 1, -2, 4] 中得到它;它的总和是 5。
好的 - 那么我们该如何解决这个问题呢?您可能会做的一件事是这样的原因:“如果我知道左半部分的最大段总和,以及右半部分的最大段总和,那么也许我可以以某种方式将它们组合在一起并找出总体上的最大段总和” .这个想法需要您找到两个子半部分上的最大段总和,这是原始问题的一个较小实例。这就是递归,因此将这个想法直接翻译成代码也是递归的。
但最大段和问题不是“递归”或“迭代”——它可以两者兼而有之,具体取决于您对解决方案的看法。我在上面给出了一个递归的思考过程。这是一个迭代过程:“好吧,如果我将每个段中从某个索引 i 开始并以某个索引 j 结束的元素相加,我可以取其中的最大值来解决问题”。直接尝试对这种方法进行编码会给您带来三重嵌套循环(并且在赋值上打一个不好的标记,因为它非常低效!)。
因此,同样的问题,取决于问题的概念化方式,可能会导致递归或迭代解决方案。现在,我碰巧选择了一个有很多解决方法的问题,并且有合理的递归和迭代解决方案。然而,有些问题只承认一种类型的解决方案,并且该解决方案可能最自然地使用递归或迭代来实现。例如,如果我要求您编写一个函数,该函数一直要求用户输入字母,直到他们输入 y 或 n,您可能会开始思考:“不断重复提示并要求输入......”并且在不知不觉中你有一些迭代代码。也许您可能会递归地思考:“如果用户输入 y 或 n,我就完成了;否则向用户询问 y 或 n”......在这种情况下,您将生成一个递归算法。但是这里的递归并没有给你太多:它不必要地使用堆栈并且不会使程序更快。 (递归有时更容易证明正确性,在这种情况下,您可能会递归地呈现某些内容,即使您可以交替给出合理的迭代解决方案。)
【讨论】: