基本上,尾递归是函数调用自身时所拥有的,但之后不需要做任何事情,除了可能返回递归调用的结果。这是递归的一个特例……不在于它是如何工作的,而是在于它很容易变成一个循环。对于不优化尾递归的编译器/解释器,这可能意味着正常工作和溢出调用堆栈之间的区别。
例如:
void countdown(int t) {
if (t <= 1) return;
printf("%d\n", t);
countdown(t - 1);
}
这是尾递归,因为在countdown 调用自身之后不需要发生任何其他事情。
对比
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
这不是尾递归的。不同的是,函数调用自身后需要重新获得控制权,所以可以将结果乘以n。
(额外的一点是:您可以使用一种称为“连续传递样式”的技术将其转换为尾递归函数。基本上,在这种情况下,您可以将结果与其他参数一起传递。)
int factorial(int n, int product = 1) {
if (n <= 1) return product;
return factorial(n - 1, product * n);
}
但我离题了。
无论如何,删除尾递归的编译器基本上只是调整当前调用帧,使变量成为调用时的样子,然后跳回到函数的开头。你的老师可能不会很喜欢那样,有几个很好的理由。但让我们从那里开始吧。
selection_sort(int a, int b){
tail_call:
if(a == b+1){
//do nothing
}else{
i = minIndex(arr, a, b);
if(i != a)
swap(arr, i, a);
// tweak the variables so the next iteration sees (a+1, b)
a += 1;
goto tail_call;
}
}
虽然我们正在重新排列东西,但空的条件块相当可怕。这也可以改写为
selection_sort(int a, int b){
tail_call:
if(a != b+1){
i = minIndex(arr, a, b);
if(i != a)
swap(arr, i, a);
// tweak the variables so the next iteration sees (a+1, b)
a += 1;
goto tail_call;
}
}
现在,您可能已经被告知goto 是魔鬼。 :) (这有点言过其实,但它不应该用在有更好选择的地方。)所以重构以摆脱它。
我们如何摆脱它?我们找到了一个控制结构,它可以完成goto 试图做的事情。在这种情况下,while 循环非常符合要求。 condition 中没有范围差异,此代码:
loop:
if (condition) {
...magic...
goto loop;
}
和这个完全一样:
while (condition) {
...magic...
}
以至于许多编译器甚至会为两者生成完全相同的代码。
所以让我们用while 循环清理代码。
selection_sort(int a, int b){
while (a != b+1) {
int i = minIndex(arr, a, b);
if(i != a)
swap(arr, i, a);
a += 1;
}
}