📜  IntroSort或自省排序

📅  最后修改于: 2021-04-25 01:11:49             🧑  作者: Mango

Introsort(自省排序)是一个基于比较的排序,由三个排序阶段组成。它们是快速排序,堆排序和插入排序。 Introsort的基本概念和C++代码在这里可用
下节介绍了Introsort算法的优缺点后,将介绍其公式。

  1. 快速排序
    快速排序是一种有效的排序算法,但在O(N ^ 2)与O(N)辅助空间进行比较时表现最差。这种最坏情况的复杂性取决于Quicksort算法的两个阶段。
    1.选择枢轴元素
    2.算法过程中的递归深度
  2. 堆排序
    Heapsort具有O(N log N)个最坏情况下的时间复杂度,这比Quicksort最差情况下的复杂度好得多。那么,很明显Heapsort是最好的吗?不,Quicksort的秘密在于它不会交换已经按顺序排列的元素,这是不必要的,而对于Heapsort,即使所有数据都已排序,该算法也会交换所有元素以对数组进行排序。同样,通过选择最佳枢轴,可以避免快速排序中O(N ^ 2)的最坏情况。但是,在无法避免的Heapsort情况下,交换将花费更多时间。因此,Quicksort优于Heapsort。
    关于Heapsort的最好的事情是,如果递归深度变得太大(如log N),则最坏情况下的复杂度仍为O(N log N)。
  3.  合并排序
    合并排序仅在O(N log N)时才具有最坏情况的复杂度。无论其大小如何,Mergesort都能在任何类型的数据集上很好地工作,而Quicksort不能与大型数据集一起很好地工作。但是,Mergesort不是就地,而Quicksort是就地,在这里起着至关重要的作用。 Mergesort与LinkedLists配合得很好,而Quicksort与数组配合得很好。使用Quicksort时,引用的位置更好,而使用Mergesort时,则不好。因此,出于常规目的,由于手头有内存限制,Quicksort优于Mergesort。
  4.  插入排序
    插入排序的主要优点是它的简单性。当处理少量列表时,它也表现出良好的性能。插入排序是一种就地排序算法,因此空间需求最小。插入排序的缺点是,当数据大小变大时,插入排序的性能将不如其他排序算法。

以下是Introsort的构成方式:
选择正确的排序算法取决于使用排序算法的场合。已有许多排序算法,各有其优缺点。因此,要获得更好的排序算法,解决方案是调整现有算法,并产生一种效果更好的新排序算法。有许多混合算法,其性能优于常规排序算法。 Introsort就是其中之一。最好的Quicksort版本在绝大多数输入上都具有堆排序和合并排序的竞争力。很少有Quicksort具有O(N ^ 2)运行时间和O(N)堆栈使用率的最坏情况。 Heapsort和Mergesort都具有O(N log N)最坏情况的运行时间,并且Heapsort的堆栈使用量为O(1),Mergesort的堆栈使用量为O(log N)。另外,如果数据集较小,则插入排序的性能比上述任何一种算法都要好。
结合排序算法的所有优点,Introsort会根据数据集运行。

  1. 如果输入中的元素数量变少,则Introsort会对输入执行插入排序。
  2. 考虑到最少数量的比较(Quicksort),为了通过查找枢轴元素来拆分数组,使用了Quicksort。前面已经引用过,Quicksort的最坏情况是基于两个阶段,这是我们如何解决它们的方法。
    1. 选择枢轴元素:我们可以使用3位中位数概念或随机枢轴概念或中位数作为枢轴概念来查找枢轴元素
    2. 算法过程中的递归深度:当递归深度变高时,Introsort使用Heapsort,因为它具有O(N log N)的确定上限。

depthLimit如何工作?
depthLimit表示递归的最大深度。通常选择它作为输入数组长度的对数(请参阅下面的实现)。这样做的目的是确保最坏情况下的时间复杂度保持为O(N log N)。请注意,HeapSort的最坏情况下的时间复杂度为O(N log N)。

为什么不使用Mergesort?
由于数组处理是Quicksort优于Mergesort的就地概念,因此我们不使用Mergesort。

可以将Introsort应用到任何地方吗?

  1. 如果数据不适合数组,则不能使用Introsort。
  2. 此外,Introsort像Quicksort和Heapsort一样不稳定。当需要稳定排序时,无法应用Introsort。

是Introsort唯一的混合排序算法吗?
否。还有其他混合排序算法,例如“混合合并排序”,“ Tim排序”,“插入合并混合”。
比较Heapsort,Insertsort,Quicksort,Introsort时对6000个元素进行排序(以毫秒为单位)。

伪代码:

sort(A : array):
    depthLimit = 2xfloor(log(length(A)))
    introsort(A, depthLimit)

introsort(A, depthLimit):
    n = length(A)
    if n<=16:
        insertionSort(A)
    if depthLimit == 0:
        heapsort(A)
    else:

        // using quick sort, the
        // partition point is found 
        p = partition(A)  
        introsort(A[0:p-1], depthLimit - 1)
        introsort(A[p+1:n], depthLimit - 1)

时间复杂度:
最坏的情况:O(nlogn)(比Quicksort好)
平均情况性能:O(nlogn)
在快速排序阶段,可以使用3位中位数概念或数组的最后一个元素来选择枢轴。对于包含大量元素的数据,中位数为3的概念会减慢Quicksort的运行时间。
在下面描述的示例中,快速排序算法基于3位中位数的概念计算枢轴元素。

例子:

Java
// Java implementation of Introsort algorithm
 
import java.io.IOException;
 
public class Introsort {
 
    // the actual data that has to be sorted
    private int a[];
 
    // the number of elements in the data
    private int n;
 
    // Constructor to initialize the size
    // of the data
    Introsort(int n)
    {
        a = new int[n];
        this.n = 0;
    }
 
    // The utility function to insert the data
    private void dataAppend(int temp)
    {
        a[n] = temp;
        n++;
    }
 
    // The utility function to swap two elements
    private void swap(int i, int j)
    {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
 
    // To maxHeap a subtree rooted with node i which is
    // an index in a[]. heapN is size of heap
    private void maxHeap(int i, int heapN, int begin)
    {
        int temp = a[begin + i - 1];
        int child;
 
        while (i <= heapN / 2) {
            child = 2 * i;
 
            if (child < heapN
                && a[begin + child - 1] < a[begin + child])
                child++;
 
            if (temp >= a[begin + child - 1])
                break;
 
            a[begin + i - 1] = a[begin + child - 1];
            i = child;
        }
        a[begin + i - 1] = temp;
    }
 
    // Function to build the heap (rearranging the array)
    private void heapify(int begin, int end, int heapN)
    {
        for (int i = (heapN) / 2; i >= 1; i--)
            maxHeap(i, heapN, begin);
    }
 
    // main function to do heapsort
    private void heapSort(int begin, int end)
    {
        int heapN = end - begin;
 
        // Build heap (rearrange array)
        this.heapify(begin, end, heapN);
 
        // One by one extract an element from heap
        for (int i = heapN; i >= 1; i--) {
 
            // Move current root to end
            swap(begin, begin + i);
 
            // call maxHeap() on the reduced heap
            maxHeap(1, i, begin);
        }
    }
 
    // function that implements insertion sort
    private void insertionSort(int left, int right)
    {
 
        for (int i = left; i <= right; i++) {
            int key = a[i];
            int j = i;
 
            // Move elements of arr[0..i-1], that are
            // greater than the key, to one position ahead
            // of their current position
            while (j > left && a[j - 1] > key) {
                a[j] = a[j - 1];
                j--;
            }
            a[j] = key;
        }
    }
 
    // Function for finding the median of the three elements
    private int findPivot(int a1, int b1, int c1)
    {
        int max = Math.max(Math.max(a[a1], a[b1]), a[c1]);
        int min = Math.min(Math.min(a[a1], a[b1]), a[c1]);
        int median = max ^ min ^ a[a1] ^ a[b1] ^ a[c1];
        if (median == a[a1])
            return a1;
        if (median == a[b1])
            return b1;
        return c1;
    }
 
    // This function takes the last element as pivot, places
    // the pivot element at its correct position in sorted
    // array, and places all smaller (smaller than pivot)
    // to the left of the pivot
    // and greater elements to the right of the pivot
    private int partition(int low, int high)
    {
 
        // pivot
        int pivot = a[high];
 
        // Index of smaller element
        int i = (low - 1);
        for (int j = low; j <= high - 1; j++) {
 
            // If the current element is smaller
            // than or equal to the pivot
            if (a[j] <= pivot) {
 
                // increment index of smaller element
                i++;
                swap(i, j);
            }
        }
        swap(i + 1, high);
        return (i + 1);
    }
 
    // The main function that implements Introsort
    // low  --> Starting index,
    // high  --> Ending index,
    // depthLimit  --> recursion level
    private void sortDataUtil(int begin, int end, int depthLimit)
    {
        if (end - begin > 16) {
            if (depthLimit == 0) {
 
                // if the recursion limit is
                // occurred call heap sort
                this.heapSort(begin, end);
                return;
            }
 
            depthLimit = depthLimit - 1;
            int pivot = findPivot(begin,
                begin + ((end - begin) / 2) + 1,
                                           end);
            swap(pivot, end);
 
            // p is partitioning index,
            // arr[p] is now at right place
            int p = partition(begin, end);
 
            // Separately sort elements before
            // partition and after partition
            sortDataUtil(begin, p - 1, depthLimit);
            sortDataUtil(p + 1, end, depthLimit);
        }
 
        else {
            // if the data set is small,
            // call insertion sort
            insertionSort(begin, end);
        }
    }
 
    // A utility function to begin the
    // Introsort module
    private void sortData()
    {
 
        // Initialise the depthLimit
        // as 2*log(length(data))
        int depthLimit
            = (int)(2 * Math.floor(Math.log(n) /
                                  Math.log(2)));
 
        this.sortDataUtil(0, n - 1, depthLimit);
    }
 
    // A utility function to print the array data
    private void printData()
    {
        for (int i = 0; i < n; i++)
            System.out.print(a[i] + " ");
    }
 
    // Driver code
    public static void main(String args[]) throws IOException
    {
        int[] inp = { 2, 10, 24, 2, 10, 11, 27,
                      4, 2, 4, 28, 16, 9, 8,
                      28, 10, 13, 24, 22, 28,
                      0, 13, 27, 13, 3, 23,
                      18, 22, 8, 8 };
 
        int n = inp.length;
        Introsort introsort = new Introsort(n);
 
        for (int i = 0; i < n; i++) {
            introsort.dataAppend(inp[i]);
        }
 
        introsort.sortData();
        introsort.printData();
    }
}


Python3
# Python implementation of Introsort algorithm
 
import math
import sys
from heapq import heappush, heappop
 
arr = []
 
 
# The main function to sort
# an array of the given size
# using heapsort algorithm
 
def heapsort():
    global arr
    h = []
 
    # building the heap
 
    for value in arr:
        heappush(h, value)
    arr = []
 
    # extracting the sorted elements one by one
 
    arr = arr + [heappop(h) for i in range(len(h))]
 
 
# The main function to sort the data using
# insertion sort algorithm
 
def InsertionSort(begin, end):
    left = begin
    right = end
 
    # Traverse through 1 to len(arr)
 
    for i in range(left + 1, right + 1):
        key = arr[i]
 
        # Move elements of arr[0..i-1], that are
        # greater than key, to one position ahead
        # of their current position
 
        j = i - 1
        while j >= left and arr[j] > key:
            arr[j + 1] = arr[j]
            j = j - 1
        arr[j + 1] = key
 
 
# This function takes last element as pivot, places
# the pivot element at its correct position in sorted
# array, and places all smaller (smaller than pivot)
# to left of pivot and all greater elements to right
# of pivot
 
def Partition(low, high):
    global arr
 
  # pivot
 
    pivot = arr[high]
 
  # index of smaller element
 
    i = low - 1
 
    for j in range(low, high):
 
        # If the current element is smaller than or
        # equal to the pivot
 
        if arr[j] <= pivot:
 
            # increment index of smaller element
 
            i = i + 1
            (arr[i], arr[j]) = (arr[j], arr[i])
    (arr[i + 1], arr[high]) = (arr[high], arr[i + 1])
    return i + 1
 
 
# The function to find the median
# of the three elements in
# in the index a, b, d
 
def MedianOfThree(a, b, d):
    global arr
    A = arr[a]
    B = arr[b]
    C = arr[d]
 
    if A <= B and B <= C:
        return b
    if C <= B and B <= A:
        return b
    if B <= A and A <= C:
        return a
    if C <= A and A <= B:
        return a
    if B <= C and C <= A:
        return d
    if A <= C and C <= B:
        return d
 
 
# The main function that implements Introsort
# low  --> Starting index,
# high  --> Ending index
# depthLimit --> recursion level
 
def IntrosortUtil(begin, end, depthLimit):
    global arr
    size = end - begin
    if size < 16:
 
        # if the data set is small, call insertion sort
 
        InsertionSort(begin, end)
        return
 
    if depthLimit == 0:
 
        # if the recursion limit is occurred call heap sort
 
        heapsort()
        return
 
    pivot = MedianOfThree(begin, begin + size // 2, end)
    (arr[pivot], arr[end]) = (arr[end], arr[pivot])
 
    # partitionPoint is partitioning index,
    # arr[partitionPoint] is now at right place
 
    partitionPoint = Partition(begin, end)
 
    # Separately sort elements before partition and after partition
 
    IntrosortUtil(begin, partitionPoint - 1, depthLimit - 1)
    IntrosortUtil(partitionPoint + 1, end, depthLimit - 1)
 
 
# A utility function to begin the Introsort module
 
def Introsort(begin, end):
 
    # initialise the depthLimit as 2 * log(length(data))
 
    depthLimit = 2 * math.floor(math.log2(end - begin))
    IntrosortUtil(begin, end, depthLimit)
 
 
# A utility function to print the array data
 
def printArr():
    print ('Arr: ', arr)
 
 
def main():
    global arr
    arr = arr + [
        2, 10, 24, 2, 10, 11, 27,
        4, 2, 4, 28, 16, 9, 8,
        28, 10, 13, 24, 22, 28,
        0, 13, 27, 13, 3, 23,
        18, 22, 8, 8 ]
         
    n = len(arr)
 
    Introsort(0, n - 1)
    printArr()
 
 
if __name__ == '__main__':
    main()


C#
// C# implementation of
// Introsort algorithm
using System;
class Introsort{
 
// the actual data that
// has to be sorted
public int []a;
 
// the number of elements
// in the data
public int n;
 
// Constructor to initialize
// the size of the data
Introsort(int n)
{
  a = new int[n];
  this.n = 0;
}
 
// The utility function to
// insert the data
private void dataAppend(int temp)
{
  a[n] = temp;
  n++;
}
 
// The utility function to
// swap two elements
private void swap(int i,
                  int j)
{
  int temp = a[i];
  a[i] = a[j];
  a[j] = temp;
}
 
// To maxHeap a subtree rooted
// with node i which is an index
// in []a. heapN is size of heap
private void maxHeap(int i,
                     int heapN,
                     int begin)
{
  int temp = a[begin + i - 1];
  int child;
 
  while (i <= heapN / 2)
  {
    child = 2 * i;
 
    if (child < heapN &&
        a[begin + child - 1] <
        a[begin + child])
      child++;
 
    if (temp >=
        a[begin + child - 1])
      break;
 
    a[begin + i - 1] = a[begin + child - 1];
    i = child;
  }
  a[begin + i - 1] = temp;
}
 
// Function to build the
// heap (rearranging the array)
private void heapify(int begin,
                     int end,
                     int heapN)
{
  for (int i = (heapN) / 2;
           i >= 1; i--)
    maxHeap(i, heapN, begin);
}
 
// main function to do heapsort
private void heapSort(int begin,
                      int end)
{
  int heapN = end - begin;
 
  // Build heap (rearrange array)
  this.heapify(begin, end, heapN);
 
  // One by one extract an element
  // from heap
  for (int i = heapN; i >= 1; i--)
  {
    // Move current root to end
    swap(begin, begin + i);
 
    // call maxHeap() on the
    // reduced heap
    maxHeap(1, i, begin);
  }
}
 
// function that implements
// insertion sort
private void insertionSort(int left,
                           int right)
{
  for (int i = left; i <= right; i++)
  {
    int key = a[i];
    int j = i;
 
    // Move elements of arr[0..i-1],
    // that are greater than the key,
    // to one position ahead
    // of their current position
    while (j > left && a[j - 1] > key)
    {
      a[j] = a[j - 1];
      j--;
    }
    a[j] = key;
  }
}
 
// Function for finding the median
// of the three elements
private int findPivot(int a1,
                      int b1, int c1)
{
  int max = Math.Max(
            Math.Max(a[a1],
                     a[b1]), a[c1]);
  int min = Math.Min(
            Math.Min(a[a1],
                     a[b1]), a[c1]);
  int median = max ^ min ^
               a[a1] ^ a[b1] ^ a[c1];
  if (median == a[a1])
    return a1;
  if (median == a[b1])
    return b1;
  return c1;
}
 
// This function takes the last element
// as pivot, places the pivot element at
// its correct position in sorted
// array, and places all smaller
// (smaller than pivot) to the left of
// the pivot and greater elements to
// the right of the pivot
private int partition(int low,
                      int high)
{
  // pivot
  int pivot = a[high];
 
  // Index of smaller element
  int i = (low - 1);
   
  for (int j = low;
           j <= high - 1; j++)
  {
    // If the current element
    // is smaller than or equal
    // to the pivot
    if (a[j] <= pivot)
    {
      // increment index of
      // smaller element
      i++;
      swap(i, j);
    }
  }
  swap(i + 1, high);
  return (i + 1);
}
 
// The main function that implements
// Introsort low  --> Starting index,
// high  --> Ending index, depthLimit
// --> recursion level
private void sortDataUtil(int begin,
                          int end,
                          int depthLimit)
{
  if (end - begin > 16)
  {
    if (depthLimit == 0)
    {
      // if the recursion limit is
      // occurred call heap sort
      this.heapSort(begin, end);
      return;
    }
 
    depthLimit = depthLimit - 1;
    int pivot = findPivot(begin, begin +
                         ((end - begin) /
                           2) + 1, end);
    swap(pivot, end);
 
    // p is partitioning index,
    // arr[p] is now at right place
    int p = partition(begin, end);
 
    // Separately sort elements
    // before partition and after
    // partition
    sortDataUtil(begin, p - 1,
                 depthLimit);
    sortDataUtil(p + 1, end,
                 depthLimit);
  }
 
  else
  {
    // if the data set is small,
    // call insertion sort
    insertionSort(begin, end);
  }
}
 
// A utility function to begin
// the Introsort module
private void sortData()
{
  // Initialise the depthLimit
  // as 2*log(length(data))
  int depthLimit = (int)(2 * Math.Floor(
                             Math.Log(n) /
                             Math.Log(2)));
 
  this.sortDataUtil(0, n - 1, depthLimit);
}
 
// A utility function to print
// the array data
private void printData()
{
  for (int i = 0; i < n; i++)
    Console.Write(a[i] + " ");
}
 
// Driver code
public static void Main(String []args)
{
  int[] inp = {2, 10, 24, 2, 10, 11, 27,
               4, 2, 4, 28, 16, 9, 8,
               28, 10, 13, 24, 22, 28,
               0, 13, 27, 13, 3, 23,
               18, 22, 8, 8};
 
  int n = inp.Length;
  Introsort introsort = new Introsort(n);
 
  for (int i = 0; i < n; i++)
  {
    introsort.dataAppend(inp[i]);
  }
 
  introsort.sortData();
  introsort.printData();
}
}
 
// This code is contributed by Rajput-Ji


输出
0 2 2 2 3 4 4 8 8 8 9 10 10 10 11 13 13 13 16 18 22 22 23 24 24 27 27 28 28 28