内省排序(Introspective sort)是一种基于比较的排序,由三个排序阶段组成。它们是快速排序、堆排序和插入排序。 Introsort 的基本概念和 C++ 代码可在此处获得
在查看了各个算法的优缺点后,以下部分将展示如何制定 Introsort 算法。
- 快速排序
Quicksort 是一种高效的排序算法,但在 O(N ^ 2) 次与 O(N) 辅助空间的比较中,性能最差。这种最坏情况的复杂性取决于快速排序算法的两个阶段。
1. 选择枢轴元素
2.算法过程中的递归深度 - 堆排序
堆排序的最坏情况时间复杂度为 O(N log N),比快速排序的最坏情况要好得多。那么,很明显 Heapsort 是最好的吗?不,Quicksort 的秘密在于它不会交换已经有序的元素,这是不必要的,而对于 Heapsort,即使所有数据都已经排序,算法也会交换所有元素以对数组进行排序.此外,通过选择最佳主元,可以在快速排序中避免 O(N ^ 2) 的最坏情况。但是,在不可避免的堆排序的情况下,交换将花费更多时间。因此,快速排序优于堆排序。
Heapsort 最好的事情是,如果递归深度变得太大,比如 (log N),最坏情况的复杂度仍然是 O(N log N)。 - 归并排序
Mergesort 的最坏情况复杂度仅为 O(N log N)。 Mergesort 可以很好地处理任何类型的数据集,而不管其大小如何,而 Quicksort 不能很好地处理大数据集。但是,Mergesort 不是就地排序,而 Quicksort 是就地排序,这在这里起着至关重要的作用。 Mergesort 适用于 LinkedLists 而 Quicksort 适用于数组。 Quicksort 的引用局部性更好,而 Mergesort 则不好。因此,出于传统目的,在手头有内存限制的情况下,Quicksort 优于 Mergesort。 - 插入排序
插入排序的主要优点是简单。它在处理小列表时也表现出良好的性能。插入排序是一种就地排序算法,因此空间需求最小。插入排序的缺点是当数据变大时,它的性能不如其他排序算法。
以下是 Introsort 的制定方式:
选择正确的排序算法取决于使用排序算法的场合。手头已经有很多排序算法,它们各有优缺点。因此,为了获得更好的排序算法,解决方案是调整现有算法并产生一种效果更好的新排序算法。有很多混合算法,其性能优于一般排序算法。 Introsort就是其中之一。 Quicksort 的最佳版本在绝大多数输入上都可以与堆排序和合并排序相媲美。 Quicksort 很少有 O(N ^ 2) 运行时间和 O(N) 堆栈使用的最坏情况。 Heapsort 和 Mergesort 都有 O(N log N) 最坏情况的运行时间,以及分别为 Heapsort 和 O(log N) 为 Mergesort 使用 O(1) 的堆栈使用。此外,如果数据集很小,插入排序的性能优于上述任何一种算法。
结合排序算法的所有优点,Introsort 的行为基于数据集。
- 如果输入中的元素数量变少,Introsort 会对输入执行插入排序。
- 考虑到最少的比较次数(快速排序),为了通过查找枢轴元素来拆分数组,使用快速排序。前面引用过,Quicksort 最坏的情况是基于两个阶段,这里是我们如何修复它们。
- 选择枢轴元素:我们可以使用中值 3 概念或随机枢轴概念或中值作为枢轴概念来查找枢轴元素
- 算法过程中的递归深度:当递归深度变大时,Introsort使用Heapsort,因为它有O(N log N)的明确上限。
深度限制如何工作?
depthLimit表示递归的最大深度。它通常被选为输入数组长度的对数(请参阅下面的实现)。这个想法是为了确保最坏情况下的时间复杂度保持 O(N log N)。请注意,HeapSort 的最坏情况时间复杂度为 O(N log N)。
为什么不使用合并排序?
由于数组正在处理快速排序优于合并排序的就地概念,因此我们没有使用合并排序。
Introsort 可以在任何地方应用吗?
- 如果数据不适合数组,则不能使用 Introsort。
- 此外,与 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 现场工作专业课程和学生竞争性编程现场课程。