我们已经讨论了(在尾递归中)如果递归调用是函数执行的最后一件事,则递归函数就是尾递归。
C++
// An example of tail recursive function
void print(int n)
{
if (n < 0)
return;
cout << " " << n;
// The last executed statement is recursive call
print(n-1);
}
Java
// An example of tail recursive function
static void print(int n)
{
if (n < 0)
return;
System.out.print(" " + n);
// The last executed statement
// is recursive call
print(n - 1);
}
// This code is contributed by rutvik_56
Python3
# An example of tail recursive function
def print(n):
if (n < 0):
return
print(" ", n)
# The last executed statement is recursive call
print(n - 1)
# This code is contributed by sanjoy_62
C#
// An example of tail recursive function
static void print(int n)
{
if (n < 0)
return;
Console.Write(" " + n);
// The last executed statement
// is recursive call
print(n - 1);
}
// This code is contributed by pratham76
C++
// Above code after tail call elimination
void print(int n)
{
start:
if (n < 0)
return;
cout << " " << n;
// Update parameters of recursive call
// and replace recursive call with goto
n = n-1
goto start;
}
C++
/* Tail recursive function for QuickSort
arr[] --> Array to be sorted,
low --> Starting index,
high --> Ending index */
void quickSort(int arr[], int low, int high)
{
if (low < high)
{
/* pi is partitioning index, arr[p] is now
at right place */
int pi = partition(arr, low, high);
// Separately sort elements before
// partition and after partition
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
// See below link for complete running code
// http://geeksquiz.com/quick-sort/
C++
/* QuickSort after tail call elimination
arr[] --> Array to be sorted,
low --> Starting index,
high --> Ending index */
void quickSort(int arr[], int low, int high)
{
start:
if (low < high)
{
/* pi is partitioning index, arr[p] is now
at right place */
int pi = partition(arr, low, high);
// Separately sort elements before
// partition and after partition
quickSort(arr, low, pi - 1);
// Update parameters of recursive call
// and replace recursive call with goto
low = pi+1;
high = high;
goto start;
}
}
// See below link for complete running code
// https://ide.geeksforgeeks.org/dbq4yl
我们还讨论了尾递归优于非尾递归,因为可以通过现代编译器优化尾递归。现代编译器基本上执行尾部调用消除来优化尾部递归代码。
如果我们仔细看一下上面的函数,我们可以使用goto删除最后一个调用。以下是消除尾音的示例。
C++
// Above code after tail call elimination
void print(int n)
{
start:
if (n < 0)
return;
cout << " " << n;
// Update parameters of recursive call
// and replace recursive call with goto
n = n-1
goto start;
}
QuickSort :另一个示例
QuickSort也是尾部递归的(请注意,MergeSort不是尾部递归的,这也是QuickSort表现更好的原因之一)
C++
/* Tail recursive function for QuickSort
arr[] --> Array to be sorted,
low --> Starting index,
high --> Ending index */
void quickSort(int arr[], int low, int high)
{
if (low < high)
{
/* pi is partitioning index, arr[p] is now
at right place */
int pi = partition(arr, low, high);
// Separately sort elements before
// partition and after partition
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
// See below link for complete running code
// http://geeksquiz.com/quick-sort/
消除尾部调用后,可以通过以下替换上述函数。
C++
/* QuickSort after tail call elimination
arr[] --> Array to be sorted,
low --> Starting index,
high --> Ending index */
void quickSort(int arr[], int low, int high)
{
start:
if (low < high)
{
/* pi is partitioning index, arr[p] is now
at right place */
int pi = partition(arr, low, high);
// Separately sort elements before
// partition and after partition
quickSort(arr, low, pi - 1);
// Update parameters of recursive call
// and replace recursive call with goto
low = pi+1;
high = high;
goto start;
}
}
// See below link for complete running code
// https://ide.geeksforgeeks.org/dbq4yl
因此,编译器的工作是识别尾部递归,在开头添加标签,并在结尾更新参数,然后添加最后的goto语句。
消除尾部呼叫中的函数堆栈框架管理:
递归使用堆栈来跟踪函数调用。每次函数调用时,都会将一个新帧推入堆栈,其中包含该调用的局部变量和数据。假设一个堆栈帧需要O(1),即恒定的内存空间,那么对于N个递归调用内存将是O(N)。
消除尾部调用降低了从O(N)到O(1)递归的空间复杂度。由于消除了函数调用,因此不会创建新的堆栈帧,并且函数将在恒定的存储空间中执行。
该函数有可能在恒定的内存空间中执行,因为在尾部递归函数,在调用语句之后没有语句,因此不需要保留父函数的状态和框架。儿童函数被调用,并立即结束,但不具有控制权返回给父函数。
由于不对返回的值执行任何计算,也没有要执行的语句,因此可以根据当前函数调用的要求修改当前帧。因此,无需保留以前的函数调用的堆栈帧,并且函数可以在恒定的内存空间中执行。这使得尾递归更快并且对内存友好。