我知道递归的一般概念。在研究quicksort算法时,我遇到了尾递归的概念。在MIT 18:30秒的视频中,快速排序算法的视频中,教授说这是一种尾递归算法。我不清楚尾递归到底意味着什么。
有人可以举例说明这个概念吗?
SO社区在此处提供了一些答案。
我知道递归的一般概念。在研究quicksort算法时,我遇到了尾递归的概念。在MIT 18:30秒的视频中,快速排序算法的视频中,教授说这是一种尾递归算法。我不清楚尾递归到底意味着什么。
有人可以举例说明这个概念吗?
SO社区在此处提供了一些答案。
Answers:
尾递归是递归的一种特殊情况,其中调用函数在进行递归调用后不再进行任何计算。例如功能
int f(int x,int y){ 如果(y == 0){ 返回x; } 返回f(x * y,y-1); }
是尾递归的(因为最终指令是递归调用),而此函数不是尾递归的:
int g(int x){ 如果(x == 1){ 返回1; } 整数y = g(x-1); 返回x * y; }
因为它会在递归调用返回后进行一些计算。
尾递归很重要,因为它可以比常规递归更有效地实现。当我们进行普通的递归调用时,我们必须将返回地址压入调用堆栈,然后跳转到被调用的函数。这意味着我们需要一个调用堆栈,其大小在递归调用的深度上是线性的。当我们有尾递归时,我们知道一旦从递归调用返回,我们也将立即返回,因此我们可以跳过整个递归函数链返回,直接返回到原始调用者。这意味着我们完全不需要所有递归调用的调用栈,并且可以将最终调用实现为简单的跳转,从而节省了空间。
def recurse(x): if x < 0 return 1; for i in range 100{ (do calculations) recurse(x)}
简而言之,尾部递归是一种递归,其中编译器可以用“ goto”命令替换递归调用,因此编译后的版本将不必增加堆栈深度。
有时设计尾部递归函数需要您创建带有附加参数的辅助函数。
例如,这不是尾递归函数:
int factorial(int x) {
if (x > 0) {
return x * factorial(x - 1);
}
return 1;
}
但这是一个尾递归函数:
int factorial(int x) {
return tailfactorial(x, 1);
}
int tailfactorial(int x, int multiplier) {
if (x > 0) {
return tailfactorial(x - 1, x * multiplier);
}
return multiplier;
}
因为编译器可以使用以下代码(伪代码)将递归函数重写为非递归函数:
int tailfactorial(int x, int multiplier) {
start:
if (x > 0) {
multiplier = x * multiplier;
x--;
goto start;
}
return multiplier;
}
编译器的规则非常简单:找到“ return thisfunction(newparameters);
”时,将其替换为“ parameters = newparameters; goto start;
”。但是,只有在直接返回递归调用返回的值的情况下,才可以这样做。
如果可以像这样替换函数中的所有递归调用,则它是尾递归函数。
我的答案是基于《计算机程序的结构和解释》一书中的解释。我强烈推荐这本书给计算机科学家。
(define (factorial n)
(if (= n 1)
1
(* n (factorial (- n 1)))))
方法A的过程形状如下:
(factorial 5)
(* 5 (factorial 4))
(* 5 (* 4 (factorial 3)))
(* 5 (* 4 (* 3 (factorial 2))))
(* 5 (* 4 (* 3 (* 2 (factorial 1)))))
(* 5 (* 4 (* 3 (* 2 (* 1)))))
(* 5 (* 4 (* 3 (* 2))))
(* 5 (* 4 (* 6)))
(* 5 (* 24))
120
(define (factorial n)
(fact-iter 1 1 n))
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
方法B的流程形状如下所示:
(factorial 5)
(fact-iter 1 1 5)
(fact-iter 1 2 5)
(fact-iter 2 3 5)
(fact-iter 6 4 5)
(fact-iter 24 5 5)
(fact-iter 120 6 5)
120
线性迭代过程(方法B)在恒定空间中运行,即使该过程是递归过程也是如此。还应该注意的是,在这种方法中,设置变量定义了在任意点viz的过程状态。{product, counter, max-count}
。这也是尾递归允许编译器优化的一项技术。
在方法A中,解释器维护着更多的隐藏信息,这些信息基本上是延迟操作的链条。
尾递归是递归的一种形式,其中递归调用是函数中的最后一条指令(这是尾部来自何处)。此外,递归调用不能由对存储先前值的存储单元的引用(该函数的参数以外的引用)组成。这样,我们不必关心先前的值,并且所有递归调用都只需一个堆栈框架就足够了。尾递归是优化递归算法的一种方法。另一个优点/优化是,有一种简便的方法可以将尾递归算法转换为使用迭代而不是递归的等效算法。是的,快速排序的算法确实是尾递归的。
QUICKSORT(A, p, r)
if(p < r)
then
q = PARTITION(A, p, r)
QUICKSORT(A, p, q–1)
QUICKSORT(A, q+1, r)
这是迭代版本:
QUICKSORT(A)
p = 0, r = len(A) - 1
while(p < r)
q = PARTITION(A, p, r)
r = q - 1
p = 0, r = len(A) - 1
while(p < r)
q = PARTITION(A, p, r)
p = q + 1