快速排序算法详解与洛谷例题实战
快速排序算法详解与洛谷例题实战
一、算法原理深度剖析
快速排序采用分治策略实现高效排序,核心思想可概括为:
排序(A[p..r])=排序(A[p..q−1])⊕基准值⊕排序(A[q+1..r]) \text{排序}(A[p..r]) = \text{排序}(A[p..q-1]) \oplus \text{基准值} \oplus \text{排序}(A[q+1..r]) 排序(A[p..r])=排序(A[p..q−1])⊕基准值⊕排序(A[q+1..r])
其中qqq为基准值位置,满足:
∀x∈A[p..q−1],x≤A[q]且∀y∈A[q+1..r],y≥A[q] \forall x \in A[p..q-1], x \leq A[q] \quad \text{且} \quad \forall y \in A[q+1..r], y \geq A[q] ∀x∈A[p..q−1],x≤A[q]且∀y∈A[q+1..r],y≥A[q]
时间复杂度分析:
- 最优情况:O(nlogn)O(n \log n)O(nlogn)(每次划分平衡)
- 最坏情况:O(n2)O(n^2)O(n2)(完全有序数组)
- 空间复杂度:O(logn)O(\log n)O(logn)(递归栈深度)
稳定性证明:
设原始序列中ai=aj(i<j)a_i = a_j(i<j)ai=aj(i<j),若基准值选择导致aia_iai与aja_jaj分属不同分区,则相对位置可能改变,故为不稳定排序。
二、算法优化策略
-
基准值三数取中法
def median_of_three(arr, low, high):mid = (low + high) // 2if arr[low] > arr[mid]:arr[low], arr[mid] = arr[mid], arr[low]if arr[low] > arr[high]:arr[low], arr[high] = arr[high], arr[low]if arr[mid] > arr[high]:arr[mid], arr[high] = arr[high], arr[mid]return mid
-
尾递归优化(减少栈深度)
void quickSort(int arr[], int low, int high) {while (low < high) {int pi = partition(arr, low, high);if (pi - low < high - pi) {quickSort(arr, low, pi - 1);low = pi + 1;} else {quickSort(arr, pi + 1, high);high = pi - 1;}} }
-
三路划分法(处理重复元素)
A[p..r]→{小于基准:A[p..i]等于基准:A[i+1..j]大于基准:A[j+1..r] A[p..r] \rightarrow \begin{cases} \text{小于基准} & : A[p..i] \\ \text{等于基准} & : A[i+1..j] \\ \text{大于基准} & : A[j+1..r] \end{cases} A[p..r]→⎩⎨⎧小于基准等于基准大于基准:A[p..i]:A[i+1..j]:A[j+1..r]
三、洛谷经典例题实战
例题1:P1177 【模板】快速排序
题目描述
给定nnn个整数,使用快速排序升序排列(1≤n≤1051 \leq n \leq 10^51≤n≤105)
输入样例
5
4 2 4 1 3
优化解法(C++)
#include <iostream>
#include <algorithm>
using namespace std;int partition(int arr[], int low, int high) {int mid = low + (high - low)/2;int pivot = arr[mid];swap(arr[mid], arr[high]);int i = low;for (int j = low; j < high; j++) {if (arr[j] < pivot) {swap(arr[i], arr[j]);i++;}}swap(arr[i], arr[high]);return i;
}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);}
}int main() {int n;cin >> n;int arr[n];for (int i = 0; i < n; i++) cin >> arr[i];quickSort(arr, 0, n-1);for (int i = 0; i < n; i++) cout << arr[i] << " ";return 0;
}
算法要点
- 使用中间位置作为基准值避免有序退化
- 单次遍历完成分区操作
- 时间复杂度稳定在O(nlogn)O(n \log n)O(nlogn)
例题2:P1923 【深基9.例4】求第 k 小的数
题目描述
给定nnn个整数和kkk,求升序排列后第kkk小的数(1≤n≤5×1061 \leq n \leq 5 \times 10^61≤n≤5×106)
快速选择算法(C++)
#include <iostream>
using namespace std;int quickSelect(int arr[], int low, int high, int k) {if (low == high) return arr[low];int pivot = arr[(low+high)/2];int i = low, j = high;while (i <= j) {while (arr[i] < pivot) i++;while (arr[j] > pivot) j--;if (i <= j) swap(arr[i++], arr[j--]);}if (k <= j) return quickSelect(arr, low, j, k);if (k >= i) return quickSelect(arr, i, high, k);return arr[k];
}int main() {int n, k;scanf("%d %d", &n, &k);int arr[n];for (int i = 0; i < n; i++) scanf("%d", &arr[i]);cout << quickSelect(arr, 0, n-1, k);return 0;
}
复杂度分析
T(n)=T(n/2)+O(n)⇒T(n)=O(n) T(n) = T(n/2) + O(n) \Rightarrow T(n) = O(n) T(n)=T(n/2)+O(n)⇒T(n)=O(n)
通过避免完全排序,将时间复杂度优化至线性阶
四、工程实践要点
-
混合排序策略
当分区大小<16<16<16时切换插入排序:void hybridSort(int arr[], int low, int high) {while (high - low > 16) {int pi = partition(arr, low, high);hybridSort(arr, low, pi-1);low = pi+1;}insertionSort(arr, low, high); }
-
内存访问优化
基准值选择公式:
pivot=arr[low+cache_line_size/sizeof(int)] \text{pivot} = \text{arr}[\text{low} + \text{cache\_line\_size}/\text{sizeof(int)}] pivot=arr[low+cache_line_size/sizeof(int)]
充分利用CPU缓存局部性原理 -
并行化实现
from concurrent.futures import ThreadPoolExecutordef parallel_quick_sort(arr):if len(arr) <= 10000:return sorted(arr)pivot = median_of_three(arr)with ThreadPoolExecutor() as executor:left = executor.submit(parallel_quick_sort, [x for x in arr if x < pivot])right = executor.submit(parallel_quick_sort, [x for x in arr if x > pivot])return left.result() + [x for x in arr if x == pivot] + right.result()
五、算法对比分析
特性 | 快速排序 | 归并排序 | 堆排序 |
---|---|---|---|
时间复杂度 | O(nlogn)O(n \log n)O(nlogn) | O(nlogn)O(n \log n)O(nlogn) | O(nlogn)O(n \log n)O(nlogn) |
空间复杂度 | O(logn)O(\log n)O(logn) | O(n)O(n)O(n) | O(1)O(1)O(1) |
稳定性 | 否 | 是 | 否 |
缓存友好性 | 优 | 差 | 差 |
最坏情况 | O(n2)O(n^2)O(n2) | O(nlogn)O(n \log n)O(nlogn) | O(nlogn)O(n \log n)O(nlogn) |
六、扩展应用场景
-
数据库索引优化
B+树节点分裂使用快速排序确定中间键值:
split_index=quick_select(keys,0,n−1,n//2) \text{split\_index} = \text{quick\_select}(\text{keys}, 0, n-1, n//2) split_index=quick_select(keys,0,n−1,n//2) -
计算机图形学
深度测试中的物体渲染顺序确定:
z-buffer 排序=quick_sort(objects,key=depth) z\text{-buffer} \text{ 排序} = \text{quick\_sort}(\text{objects}, \text{key}=\text{depth}) z-buffer 排序=quick_sort(objects,key=depth) -
机器学习
特征选择时的信息增益排序:
feature_rank=argsort(−information_gain) \text{feature\_rank} = \text{argsort}(-\text{information\_gain}) feature_rank=argsort(−information_gain)