一、算法原理与分治思想
快速排序采用典型的分治策略(Divide-and-Conquer),核心操作分为三个阶段:
1.分解(Partitioning)
- 选定基准元素(pivot)
- 将数组划分为两个子区间:左区间的元素均 ≤ pivot,右区间的元素均 ≥ pivot
2.递归求解
- 对左右子区间递归执行快速排序
3.合并(Implicit Merge)
- 由于排序在分解过程中完成,无需显式合并操作
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); // 处理右区间
}
}
二、关键操作:分区算法
1. Lomuto分区法(经典实现)
核心思想: 通过单次遍历完成元素分类
int partition(int arr[], int low, int high) {
int pivot = arr[high]; // 选择末尾元素作为基准
int i = low - 1; // 小于基准区的边界指针
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr[i], arr[j]); // 将小元素交换到左区
}
}
swap(arr[i + 1], arr[high]); // 基准归位到正确位置
return i + 1; // 返回基准索引
}
实例分析(数组[3,1,4,2,5]):
初始状态:i=-1, pivot=5
j=0: 3<=5 → i=0 → swap(arr[0],arr[0]) → 无变化
j=1: 1<=5 → i=1 → swap(arr[1],arr[1]) → 无变化
j=2: 4<=5 → i=2 → swap(arr[2],arr[2]) → 无变化
j=3: 2<=5 → i=3 → swap(arr[3],arr[3]) → 无变化
最终交换arr[4]与arr[4] → 基准归位
2. Hoare分区法(原始实现)
优势: 减少元素交换次数,适合存在大量重复元素的场景
int partitionHoare(int arr[], int low, int high) {
int pivot = arr[(low + high) / 2]; // 中间元素作为基准
int i = low - 1;
int j = high + 1;
while (true) {
do { i++; } while (arr[i] < pivot);
do { j--; } while (arr[j] > pivot);
if (i >= j) return j;
swap(arr[i], arr[j]);
}
}
执行过程对比(数组[5,3,8,4,2,7,1,6]):
初始选择中间元素4为基准
i从左侧找到5>4,j从右侧找到1<4
交换5和1 → [1,3,8,4,2,7,5,6]
继续扫描直到i=2(8), j=4(2) → 交换 → [1,3,2,4,8,7,5,6]
最终i=3, j=2 → 返回j=2
三、时间复杂度分析
1.最佳情况(平衡划分)
-
每次划分产生近似相等的子问题
-
递归式:T(n) = 2T(n/2) + O(n) → O(n log n)
2.最差情况(完全失衡)
-
每次划分产生空子区间(如已排序数组选择首元素为基准)
-
T(n) = T(n-1) + O(n) → O(n²)
3.平均情况
-
数学期望证明为O(n log n)
-
实际应用中接近最佳情况
四、关键优化策略
1.三数取中法(Median-of-Three)
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]);
swap(arr[mid], arr[high-1]); // 将中位数放到high-1位置
return partition(arr, low, high);
2.尾递归优化
void quickSortTail(int arr[], int low, int high) {
while (low < high) {
int pi = partition(arr, low, high);
if (pi - low < high - pi) {
quickSortTail(arr, low, pi-1);
low = pi + 1;
} else {
quickSortTail(arr, pi+1, high);
high = pi - 1;
}
}
}
3.混合排序策略
- 当子数组长度≤16时切换为插入排序
void hybridSort(int arr[], int low, int high) {
if (high - low > 16) {
int pi = partition(arr, low, high);
hybridSort(arr, low, pi-1);
hybridSort(arr, pi+1, high);
} else {
insertionSort(arr, low, high);
}
}
五、稳定性与空间复杂度
1.不稳定性示例:
-
原始数组:[(5,a), (3,b), (5,c), (2,d)]
-
排序过程可能变为:[2,d, 3,b, 5,c, 5,a]
2.空间复杂度:
-
最佳递归深度:O(log n)
-
最差递归深度:O(n)
六、完整C++实现
#include <iostream>
#include <algorithm>
using namespace std;
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]);
swap(arr[mid], arr[high-1]);
return arr[high-1];
}
int optimizedPartition(int arr[], int low, int high) {
int pivot = medianOfThree(arr, low, high);
int i = low - 1;
for (int j = low; j < high-1; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr[i], arr[j]);
}
}
swap(arr[i+1], arr[high-1]);
return i+1;
}
void quickSort(int arr[], int low, int high) {
if (high - low > 16) {
if (low < high) {
int pi = optimizedPartition(arr, low, high);
quickSort(arr, low, pi-1);
quickSort(arr, pi+1, high);
}
} else {
// 插入排序实现略
}
}
// 测试用例
int main() {
int data[] = {9,7,5,11,12,2,14,3,10,6};
int n = sizeof(data)/sizeof(data[0]);
quickSort(data, 0, n-1);
for (int i=0; i<n; i++)
cout << data[i] << " ";
return 0;
}
七、算法扩展:三路快速排序
处理含大量重复元素的优化方案:
void quickSort3Way(int arr[], int low, int high) {
if (high <= low) return;
int lt = low, gt = high;
int pivot = arr[low];
int i = low;
while (i <= gt) {
if (arr[i] < pivot) {
swap(arr[lt++], arr[i++]);
} else if (arr[i] > pivot) {
swap(arr[i], arr[gt--]);
} else {
i++;
}
}
quickSort3Way(arr, low, lt-1);
quickSort3Way(arr, gt+1, high);
}
该实现将数组划分为三部分:
-
小于pivot(< pivot)
-
等于pivot(== pivot)
-
大于pivot(> pivot)
八、工程实践建议
1.在标准库实现中(如C++ STL的std::sort),通常结合:
-
快速排序作为主算法
-
堆排序作为递归深度保护(IntroSort)
-
插入排序处理小规模数据
2.避免递归过深的措施:
-
监控递归深度
-
超过2log₂n层后切换堆排序
3.内存优化方向:
-
使用迭代代替递归
-
优先处理较小分区