Language agnostic 关于递归的一般问题

Language agnostic 关于递归的一般问题,language-agnostic,recursion,performance,Language Agnostic,Recursion,Performance,据我所知,好的递归解决方案可以使复杂的问题变得更容易。无论是在时间上还是在空间上,它们都可以更有效 我的问题是:这不是免费的,调用堆栈将非常深。它将消耗大量内存。我说得对吗 Cons: It is hard (especially for inexperienced programmers) to think recursively There also exists the problem of stack overflow when using some forms of recursi

据我所知,好的递归解决方案可以使复杂的问题变得更容易。无论是在时间上还是在空间上,它们都可以更有效

我的问题是:这不是免费的,调用堆栈将非常深。它将消耗大量内存。我说得对吗

Cons:
 It is hard (especially for inexperienced programmers) to think recursively
 There also exists the problem of stack overflow when using some forms of recursion (head
recursion).
 It is usually less efficient because of having to push and pop recursions on and off the
run-time stack, so it can be slower to run than simple iteration.
但是为什么我们要费心使用递归呢

Pros:
 It is easier to code a recursive solution once one is able to identify that solution. The
recursive code is usually smaller, more concise, more elegant, and possibly even easier to
understand, though that depends on one’s thinking style☺
 There are some problems that are very difficult to solve without recursion. Those problems
that require backtracking such as searching a maze for a path to an exit or tree based
operations are best solved recursively.

实际上,调用堆栈不会很深。例如,像快速排序这样的分而治之算法将问题分成两部分。使用深度为32的调用堆栈,您可以对4G元素进行排序,这些元素甚至可能无法放入普通计算机的内存中。内存消耗不是一个真正的问题,它是一个堆栈,只要你没有用完它,它是免费的。。(对于32个级别,每个级别都有大量数据要存储)


如果在堆栈结构中维护堆上的状态,则可以将几乎所有的恢复过程重写为迭代过程,但这只会使代码复杂化。您可以从重写中获得真正好处的主要场景是,您有一个尾部递归代码,它不需要维护每个递归调用的状态。请注意,对于某些语言(大多数函数式编程语言和C/C++,可能还有Java),一个好的编译器可以为您做到这一点。

很难精确地确定递归所涉及的权衡

在数学抽象层面上,递归为描述函数的显式行为提供了强大的框架。例如,我们可以在数学上定义阶乘为

x! = 1             if x == 0
x! = x * (x - 1)!  else
或者我们可以递归地定义一个更复杂的函数,比如我们如何计算“N选择K”:

这段代码非常快,相应的迭代代码可能会更慢,更难阅读,更难理解

因此,简而言之,递归既不是万灵丹,也不是一种必须避免的力量。它有助于阐明许多问题的结构,否则这些问题可能看起来很难或几乎不可能。虽然它通常会导致代码更清晰,但这样做往往会以时间和内存为代价(尽管它不一定会自动降低效率;在许多情况下,它可能会更高效)。这绝对值得学习,以提高你的整体算法思维和解决问题的能力,即使你从未编写过另一个递归函数


希望这有帮助

那要看情况而定。递归最适合的问题将抵抗这个问题。一个常见的例子是Mergesort,在这个例子中,对N个项目的列表进行排序时,大约有log2(N)个堆栈帧。因此,如果堆栈帧限制为200,并且在调用Mergesort时已使用了50,那么仍然可以对大约2^150个项目进行排序,而不会出现堆栈溢出。此外,Mergesort不会为每个堆栈帧创建大量内存,因此Mergesort的总内存使用量不应明显超过原始列表的两倍

此外,一些语言(Scheme是一个很好的例子)使用递归编写代码,然后优化或编译成迭代循环。这是LISP作为一种功能语言,在执行速度方面仍然能够与C和C++竞争的方法之一。 还有一种称为的技术,可以用来执行看似递归的操作,而不产生深层调用堆栈。但是,除非它被构建到一个库中,或者甚至是一个语言级别的构造中,否则这种技术在生产率方面的优势就不那么明显了(在我看来)


因此,尽管在很多情况下很难反对xrange(10)中x的一个好的
循环,但递归确实有它的位置。

只有当您的递归不是尾部递归,并且您的语言不支持尾部递归时,它才是昂贵的。有关此主题的讨论,请参阅以下维基百科关于尾部调用的文章:


否则,它会使代码更易于阅读和测试。

这取决于问题

如果问题需要递归,比如深度优先树行走,那么避免递归的唯一方法就是编写自己的堆栈来模拟它。那救不了什么

如果问题不需要递归,比如通常的ho-hum阶乘或斐波那契函数,那有什么意义呢?你使用它不会得到任何东西


这是一个相当小的问题类别,你甚至可能有一个合理的选择。

+1我希望有更多的答案,所以我们也像这一个一样深思熟虑。如果可以,我会+2。优化尾部调用的编译器将失去报告所有堆栈帧的能力。基于此,我认为C、C++或java编译器不这么做。但我可能弄错了。我看到C编译器这样做。Java JIT进行非常复杂的优化,所以如果他们这样做,我不会感到惊讶。
C(n, k) = 1                             if k == 0
C(n, k) = 0                             if k < 0 or if n > k
C(n, k) = C(n - 1, k) + C(n - 1, k - 1) else
int Fibonacci(int n) {
    if (n <= 1) return n;
    return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Mergesort(array, low, high) {
    if (low >= high - 1) return;
    Mergesort(array, low, low + (high - low) / 2);
    Mergesort(array, low + (high - low) / 2, high);
    Merge(array, low, low + (high - low) / 2, high);
}