排序算法是计算机科学中最基础也是最重要的知识之一。快速排序(Quicksort)因其平均时间复杂度为 O(n log n) 而广受欢迎,但在最坏情况下会退化到 O(n²)。为了克服这一缺点,自省排序(Introsort) 应运而生,它结合了快速排序、堆排序和插入排序的优点,成为C++标准库(std::sort
)的默认实现。
本文将详细介绍:
快速排序的原理与优化
自省排序的设计思想
C语言实现自省排序
性能分析与对比
1. 快速排序(Quicksort)回顾
快速排序由Tony Hoare于1959年提出,采用 分治法(Divide and Conquer):
选择基准值(Pivot):通常取第一个、最后一个或随机元素。
分区(Partition):将数组分为两部分,左边 ≤ pivot,右边 ≥ pivot。
递归排序:对左右子数组重复上述过程。
C语言实现(经典快速排序)
c
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp; }int partition(int arr[], int low, int high) {int pivot = arr[high]; // 选择最后一个元素作为基准int i = low - 1; // i 指向小于pivot的区域的末尾for (int j = low; j < high; j++) {if (arr[j] <= pivot) {i++;swap(&arr[i], &arr[j]);}}swap(&arr[i + 1], &arr[high]); // 将pivot放到正确位置return i + 1; }void quickSort(int arr[], int low, int high) {if (low < high) {int pi = partition(arr, low, high);quickSort(arr, low, pi - 1);quickSort(arr, pi + 1, high);} }
快速排序的缺点
最坏情况(如数组已有序或逆序):递归深度 O(n),时间复杂度 O(n²)。
对小数组效率低:递归调用开销大。
2. 自省排序(Introsort)
自省排序由David Musser于1997年提出,结合了:
快速排序(主算法,高效分区)
堆排序(防止最坏情况)
插入排序(优化小数组)
核心思想
递归深度限制:
如果递归深度超过 2 log₂n,切换到堆排序(保证最坏情况 O(n log n))。
小数组优化:
当子数组大小 ≤ 16(经验值),改用插入排序(减少递归开销)。
三数取中法(Median-of-Three):
优化基准值选择,减少最坏情况概率。
3. C语言实现自省排序
c
#include <stdio.h> #include <stdlib.h> #include <math.h>// 交换两个元素 void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp; }// 插入排序(用于小数组优化) void insertionSort(int arr[], int low, int high) {for (int i = low + 1; i <= high; i++) {int key = arr[i];int j = i - 1;while (j >= low && arr[j] > key) {arr[j + 1] = arr[j];j--;}arr[j + 1] = key;} }// 堆排序(用于防止快速排序退化) void heapify(int arr[], int n, int i) {int largest = i;int left = 2 * i + 1;int right = 2 * i + 2;if (left < n && arr[left] > arr[largest])largest = left;if (right < n && arr[right] > arr[largest])largest = right;if (largest != i) {swap(&arr[i], &arr[largest]);heapify(arr, n, largest);} }void heapSort(int arr[], int low, int high) {int n = high - low + 1;for (int i = n / 2 - 1; i >= 0; i--)heapify(arr, n, i);for (int i = n - 1; i > 0; i--) {swap(&arr[0], &arr[i]);heapify(arr, i, 0);} }// 三数取中法选择基准值 int medianOfThree(int arr[], int low, int high) {int mid = low + (high - low) / 2;if (arr[low] > arr[mid])swap(&arr[low], &arr[mid]);if (arr[low] > arr[high])swap(&arr[low], &arr[high]);if (arr[mid] > arr[high])swap(&arr[mid], &arr[high]);return mid; // 返回中间值的索引 }// 自省排序核心 void introSortUtil(int arr[], int low, int high, int depthLimit) {int size = high - low + 1;// 小数组优化:改用插入排序if (size <= 16) {insertionSort(arr, low, high);return;}// 递归深度超限:改用堆排序if (depthLimit == 0) {heapSort(arr, low, high);return;}// 三数取中法选择基准值int pivotIdx = medianOfThree(arr, low, high);swap(&arr[pivotIdx], &arr[high]);// 快速排序分区int pi = partition(arr, low, high);// 递归排序左右子数组introSortUtil(arr, low, pi - 1, depthLimit - 1);introSortUtil(arr, pi + 1, high, depthLimit - 1); }// 自省排序入口 void introSort(int arr[], int n) {int depthLimit = 2 * log2(n); // 计算最大递归深度introSortUtil(arr, 0, n - 1, depthLimit); }
4. 性能对比
算法 | 最佳 | 平均 | 最坏 | 适用场景 |
---|---|---|---|---|
快速排序 | O(n log n) | O(n log n) | O(n²) | 通用排序 |
堆排序 | O(n log n) | O(n log n) | O(n log n) | 最坏情况保证 |
插入排序 | O(n) | O(n²) | O(n²) | 小数组优化 |
自省排序 | O(n log n) | O(n log n) | O(n log n) | 综合最优 |
5. 总结
自省排序 = 快速排序 + 堆排序 + 插入排序,综合了三种算法的优势。
防止快速排序退化:递归深度超限时切换堆排序。
优化小数组:改用插入排序减少递归开销。
C++ STL的
std::sort
就是基于自省排序,适用于绝大多数场景。
适用场景:
大规模数据排序(如数据库、科学计算)。
需要稳定高效排序(避免最坏情况)。