📜  QuickSort尾部调用优化(将最坏情况的空间减少到Log n)

📅  最后修改于: 2021-04-28 16:53:15             🧑  作者: Mango

先决条件:消除尾声

在QuickSort中,分区函数是就位的,但是我们需要额外的空间来递归调用函数。 QuickSort的简单实现对其进行两次调用,最坏的情况是在函数调用堆栈上需要O(n)空间。

最坏的情况发生在选定的枢轴始终将数组划分为一个部分包含0个元素而另一部分包含n-1个元素的情况下。例如,在下面的代码中,如果我们选择最后一个元素作为枢轴,则排序数组的情况最糟(有关可视化的信息,请参见此内容)

C
/* A Simple implementation of QuickSort that makes two
   two recursive calls. */
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/


Java
// A Simple implementation of QuickSort that
// makes two recursive calls.
static 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);
    }
}
 
// This code is contributed by rutvik_56


Python3
# Python3 program for the above approach
def quickSort(arr, low, high):
     
    if (low < high):
     
        # pi is partitioning index, arr[p] is now
        # at right place
        pi = partition(arr, low, high)
   
        # Separately sort elements before
        # partition and after partition
        quickSort(arr, low, pi - 1)
        quickSort(arr, pi + 1, high)
 
# This code is contributed by sanjoy_62


C#
// A Simple implementation of QuickSort that
// makes two recursive calls.
static 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);
    }
}
 
// This code is contributed by pratham76.


C
/* QuickSort after tail call elimination using while loop */
void quickSort(int arr[], int low, int high)
{
    while (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);
 
        low = pi+1;
    }
}
// See below link for complete running code
// https://ide.geeksforgeeks.org/qrlM31


C
/* This QuickSort requires O(Log n) auxiliary space in
   worst case. */
void quickSort(int arr[], int low, int high)
{
    while (low < high)
    {
        /* pi is partitioning index, arr[p] is now
           at right place */
        int pi = partition(arr, low, high);
 
        // If left part is smaller, then recur for left
        // part and handle right part iteratively
        if (pi - low < high - pi)
        {
            quickSort(arr, low, pi - 1);
            low = pi + 1;
        }
 
        // Else recur for right part
        else
        {
            quickSort(arr, pi + 1, high);
            high = pi - 1;
        }
    }
}
// See below link for complete running code
// https://ide.geeksforgeeks.org/LHxwPk


我们可以减少函数调用堆栈的辅助空间吗?
我们可以将辅助空间限制为O(Log n)。这个想法是基于消除尾音。如前一篇文章所述,我们可以转换代码,以便进行一次递归调用。例如,在下面的代码中,我们将上面的代码转换为使用while循环,并减少了递归调用的数量。

C

/* QuickSort after tail call elimination using while loop */
void quickSort(int arr[], int low, int high)
{
    while (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);
 
        low = pi+1;
    }
}
// See below link for complete running code
// https://ide.geeksforgeeks.org/qrlM31

尽管减少了递归调用的数量,但是在最坏的情况下,上面的代码仍然可以使用O(n)辅助空间。在最坏的情况下,可能会以第一部分始终具有n-1个元素的方式划分数组。例如,当最后一个元素是无形动产作为枢轴并且阵列被以递减顺序进行排序,这可能发生。

我们可以优化上面的代码,以仅对分区后的较小部分进行递归调用。以下是此想法的实现。

进一步优化:

C

/* This QuickSort requires O(Log n) auxiliary space in
   worst case. */
void quickSort(int arr[], int low, int high)
{
    while (low < high)
    {
        /* pi is partitioning index, arr[p] is now
           at right place */
        int pi = partition(arr, low, high);
 
        // If left part is smaller, then recur for left
        // part and handle right part iteratively
        if (pi - low < high - pi)
        {
            quickSort(arr, low, pi - 1);
            low = pi + 1;
        }
 
        // Else recur for right part
        else
        {
            quickSort(arr, pi + 1, high);
            high = pi - 1;
        }
    }
}
// See below link for complete running code
// https://ide.geeksforgeeks.org/LHxwPk

在上面的代码中,如果左侧部分变小,则对左侧部分进行递归调用。否则为正确的部分。在最坏的情况下(对于空间),在所有递归调用中两个部分的大小均相等时,我们使用O(Log n)额外空间。

参考:
http://www.cs.nthu.edu.tw/~wkhon/algo08-tutorials/tutorial2b.pdf