快速排序是面试和竞赛中最常考的排序算法。本文对比 3 种经典实现:Lomuto 分区、Hoare 分区和三数取中优化版,分析各自的适用场景。
一、Lomuto 分区
思路简单:选择最后一个元素作为 pivot,将小于 pivot 的元素放到左边,大于的放到右边。
int lomuto_partition(int arr[], int lo, int hi) { int pivot = arr[hi]; int i = lo; for (int j = lo; j < hi; j++) { if (arr[j] < pivot) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; i++; } } int tmp = arr[i]; arr[i] = arr[hi]; arr[hi] = tmp; return i; } void quicksort_lomuto(int arr[], int lo, int hi) { if (lo < hi) { int p = lomuto_partition(arr, lo, hi); quicksort_lomuto(arr, lo, p - 1); quicksort_lomuto(arr, p + 1, hi); } }
缺点:交换次数多,对重复元素处理不佳(所有等于 pivot 的元素都会被分到右边)。
二、Hoare 分区
双指针从两端向中间扫描,找到左侧大于 pivot 和右侧小于 pivot 的元素后交换。交换次数更少,分区更均衡。
int hoare_partition(int arr[], int lo, int hi) { int pivot = arr[(lo + hi) / 2]; int i = lo - 1, j = hi + 1; while (1) { do i++; while (arr[i] < pivot); do j--; while (arr[j] > pivot); if (i >= j) return j; int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } void quicksort_hoare(int arr[], int lo, int hi) { if (lo < hi) { int p = hoare_partition(arr, lo, hi); quicksort_hoare(arr, lo, p); quicksort_hoare(arr, p + 1, hi); } }
三、三数取中优化
对于已排序或接近排序的数据,固定选择 pivot 会导致最坏情况 O(n²)。三数取中法选择首、中、末三个元素的中位数作为 pivot,有效避免退化。
int median_of_three(int arr[], int lo, int hi) { int mid = (lo + hi) / 2; if (arr[lo] > arr[mid]) { int t = arr[lo]; arr[lo] = arr[mid]; arr[mid] = t; } if (arr[lo] > arr[hi]) { int t = arr[lo]; arr[lo] = arr[hi]; arr[hi] = t; } if (arr[mid] > arr[hi]) { int t = arr[mid]; arr[mid] = arr[hi]; arr[hi] = t; } int t = arr[mid]; arr[mid] = arr[hi - 1]; arr[hi - 1] = t; return arr[hi - 1]; }
四、三种实现对比
- Lomuto:代码简单,适合教学;交换次数多,重复元素性能差
- Hoare:交换次数少约 3 倍;分区更均衡;递归深度更小
- 三数取中:避免已排序数据的最坏情况;实际运行中最稳定
工程建议:实际项目中使用 Hoare 分区 + 三数取中 + 小数组切换插入排序,可获得接近理论最优的性能。