📜  了解您的排序算法|第2组(Introsort- C++的排序武器)

📅  最后修改于: 2021-05-04 10:23:06             🧑  作者: Mango

在上一篇文章中,我们讨论了不同语言使用的排序武器。在本文中,将讨论C++的排序武器,Introsort。

什么是Introsort?
简而言之,它是最好的排序算法。它是一种混合排序算法,这意味着它使用多个排序算法作为例程。

Introsort使用哪些标准排序算法
Introsort是一种混合排序算法,它使用三种排序算法来最大程度地减少运行时间:Quicksort,Heapsort和Insertion Sort

它是如何工作的?
Introsort从quicksort开始,如果递归深度超过特定限制,它将切换到Heapsort,以避免Quicksort的最差情况O(N 2 )时间复杂度。当要排序的元素数量很少时,它也使用插入排序。
因此,首先创建一个分区。从这里出现了三种情况。

  1. 如果分区大小足以超过最大深度限制,则Introsort会切换到Heapsort。我们将最大深度限制定义为2 * log(N)
  2. 如果分区大小太小,则“快速排序”将衰减为“插入排序”。我们将此临界值定义为16(由于研究)。因此,如果分区大小小于16,那么我们将进行插入排序。
  3. 如果分区大小在限制范围内并且不太小(即-在16和2 * log(N)之间),则它将执行简单的快速排序。

为什么它比简单的Quicksort更好,或者为什么需要Introsort?
由于Quicksort可能具有更糟的O(N 2 )时间复杂度,并且还会增加递归堆栈空间(如果应用了尾递归,则会增加O(log N)),因此要避免所有这些情况,我们需要将算法从Quicksort切换到另一个如果有可能发生更糟的情况。因此,Introsort通过切换到Heapsort解决了此问题。

同样由于较大的常数因子,当N足够小时,快速排序的性能甚至会比O(N2)排序算法差。因此,它切换到插入排序以减少排序的运行时间。

同样,如果完成了错误的枢轴选择,那么快速排序的效果就不会比气泡排序更好。

为什么要使用插入排序(而不是冒泡排序等)?
插入排序具有以下优点。

  1. 众所周知,事实是插入排序是针对小型数组的最佳基于比较的排序算法。
  2. 它具有很好的参考位置
  3. 它是一种自适应排序算法,即,如果对数组元素进行部分排序,则其性能优于所有其他算法。

为什么要使用Heapsort(而不是Mergesort等)?
这完全是由于内存需求。合并排序需要O(N)空间,而Heapsort是就地O(1)空间算法。

当分区大小小于限制时,为什么不使用Heapsort代替Quicksort?
这个问题与为什么Quicksort通常胜过Heapsort?

答案是,尽管Heapsort的平均值也平均为O(N log N),甚至更糟的情况和O(1)空间,但是当分区大小在限制范围内时,我们仍然不使用它,因为额外的隐藏常数因子在Heapsort中比在Quicksort中要大得多。


为什么要使用中断16从快速排序切换到插入排序,并使用2 * logN从快速排序切换到堆排序?

由于进行了各种测试和研究,因此根据经验选择了这些值。

/* A Program to sort the array using Introsort.
  The most popular C++ STL Algorithm- sort()
  uses Introsort. */
  
#include
using namespace std;
  
// A utility function to swap the values pointed by
// the two pointers
void swapValue(int *a, int *b)
{
    int *temp = a;
    a = b;
    b = temp;
    return;
}
  
/* Function to sort an array using insertion sort*/
void InsertionSort(int arr[], int *begin, int *end)
{
    // Get the left and the right index of the subarray
    // to be sorted
    int left = begin - arr;
    int right = end - arr;
  
    for (int i = left+1; i <= right; i++)
    {
        int key = arr[i];
        int j = i-1;
  
       /* Move elements of arr[0..i-1], that are
          greater than key, to one position ahead
          of their current position */
        while (j >= left && arr[j] > key)
        {
            arr[j+1] = arr[j];
            j = j-1;
        }
        arr[j+1] = key;
   }
  
   return;
}
  
// A function to partition the array and return
// the partition point
int* Partition(int arr[], int low, int high)
{
    int pivot = arr[high];    // pivot
    int i = (low - 1);  // Index of smaller element
  
    for (int j = low; j <= high- 1; j++)
    {
        // If current element is smaller than or
        // equal to pivot
        if (arr[j] <= pivot)
        {
            // increment index of smaller element
            i++;
  
            swap(arr[i], arr[j]);
        }
    }
    swap(arr[i + 1], arr[high]);
    return (arr + i + 1);
}
  
  
// A function that find the middle of the
// values pointed by the pointers a, b, c
// and return that pointer
int *MedianOfThree(int * a, int * b, int * c)
{
    if (*a < *b && *b < *c)
        return (b);
  
    if (*a < *c && *c <= *b)
        return (c);
  
    if (*b <= *a && *a < *c)
        return (a);
  
    if (*b < *c && *c <= *a)
        return (c);
  
    if (*c <= *a && *a < *b)
        return (a);
  
    if (*c <= *b && *b <= *a)
        return (b);
}
  
// A Utility function to perform intro sort
void IntrosortUtil(int arr[], int * begin,
                  int * end, int depthLimit)
{
    // Count the number of elements
    int size = end - begin;
  
      // If partition size is low then do insertion sort
    if (size < 16)
    {
        InsertionSort(arr, begin, end);
        return;
    }
  
    // If the depth is zero use heapsort
    if (depthLimit == 0)
    {
        make_heap(begin, end+1);
        sort_heap(begin, end+1);
        return;
    }
  
    // Else use a median-of-three concept to
    // find a good pivot
    int * pivot = MedianOfThree(begin, begin+size/2, end);
  
    // Swap the values pointed by the two pointers
    swapValue(pivot, end);
  
   // Perform Quick Sort
    int * partitionPoint = Partition(arr, begin-arr, end-arr);
    IntrosortUtil(arr, begin, partitionPoint-1, depthLimit - 1);
    IntrosortUtil(arr, partitionPoint + 1, end, depthLimit - 1);
  
    return;
}
  
/* Implementation of introsort*/
void Introsort(int arr[], int *begin, int *end)
{
    int depthLimit = 2 * log(end-begin);
  
    // Perform a recursive Introsort
    IntrosortUtil(arr, begin, end, depthLimit);
  
      return;
}
  
// A utility function ot print an array of size n
void printArray(int arr[], int n)
{
   for (int i=0; i < n; i++)
       printf("%d ", arr[i]);
   printf("\n");
}
  
// Driver program to test Introsort
int main()
{
    int arr[] = {3, 1, 23, -9, 233, 23, -313, 32, -9};
    int n = sizeof(arr) / sizeof(arr[0]);
  
    // Pass the array, the pointer to the first element and
    // the pointer to the last element
    Introsort(arr, arr, arr+n-1);
    printArray(arr, n);
  
    return(0);
}
输出:

-313 -9 -9 1 3 23 23 32 233


Introsort稳定吗?

由于Quicksort也不稳定,因此Introsort也不稳定。

时间复杂度
最佳情况– O(N log N)
平均情况-O(N log N)
更差的情况-O(N log N)
其中,N =要排序的元素数。

辅助空间
就像快速排序一样,它可以使用O(log N)辅助递归堆栈空间。

了解您的排序算法|第2组(Introsort- C++的排序武器)

参考
https://zh.wikipedia.org/wiki/Introsort