📜  IntroSort 或内省排序

📅  最后修改于: 2021-09-16 11:18:58             🧑  作者: Mango

内省排序(Introspective sort)是一种基于比较的排序,由三个排序阶段组成。它们是快速排序、堆排序和插入排序。 Introsort 的基本概念和 C++ 代码可在此处获得
在查看了各个算法的优缺点后,以下部分将展示如何制定 Introsort 算法。

  1. 快速排序
    Quicksort 是一种高效的排序算法,但在 O(N ^ 2) 次与 O(N) 辅助空间的比较中,性能最差。这种最坏情况的复杂性取决于快速排序算法的两个阶段。
    1. 选择枢轴元素
    2.算法过程中的递归深度
  2. 堆排序
    堆排序的最坏情况时间复杂度为 O(N log N),比快速排序的最坏情况要好得多。那么,很明显 Heapsort 是最好的吗?不,Quicksort 的秘密在于它不会交换已经有序的元素,这是不必要的,而对于 Heapsort,即使所有数据都已经排序,算法也会交换所有元素以对数组进行排序.此外,通过选择最佳主元,可以在快速排序中避免 O(N ^ 2) 的最坏情况。但是,在不可避免的堆排序的情况下,交换将花费更多时间。因此,快速排序优于堆排序。
    Heapsort 最好的事情是,如果递归深度变得太大,比如 (log N),最坏情况的复杂度仍然是 O(N log N)。
  3.  归并排序
    Mergesort 的最坏情况复杂度仅为 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(log N) 为 Mergesort 使用 O(1) 的堆栈使用。此外,如果数据集很小,插入排序的性能优于上述任何一种算法。
结合排序算法的所有优点,Introsort 的行为基于数据集。

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

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

为什么不使用合并排序?
由于数组正在处理快速排序优于合并排序的就地概念,因此我们没有使用合并排序。

Introsort 可以在任何地方应用吗?

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

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 的概念会减慢快速排序的运行时间。
在下面描述的示例中,快速排序算法基于中值 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 

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程