📜  消除尾声

📅  最后修改于: 2021-04-27 22:47:45             🧑  作者: Mango

我们已经讨论了(在尾递归中)如果递归调用是函数执行的最后一件事,则递归函数就是尾递归。

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)递归的空间复杂度。由于消除了函数调用,因此不会创建新的堆栈帧,并且函数将在恒定的存储空间中执行。

该函数有可能在恒定的内存空间中执行,因为在尾部递归函数,在调用语句之后没有语句,因此不需要保留父函数的状态和框架。儿童函数被调用,并立即结束,但不具有控制权返回给父函数。

由于不对返回的值执行任何计算,也没有要执行的语句,因此可以根据当前函数调用的要求修改当前帧。因此,无需保留以前的函数调用的堆栈帧,并且函数可以在恒定的内存空间中执行。这使得尾递归更快并且对内存友好。