-快速排序(英语:Quicksort),又称分区交换排序(partition-exchange sort),简称快排,是一种被广泛运用的排序算法。
+本页面将简要介绍快速排序。
+
+## 简介
+
+快速排序(英语:Quicksort),又称分区交换排序(英语:partition-exchange sort),简称快排,是一种被广泛运用的排序算法。
- ## 工作原理
+ ## 基本原理与实现
+
+ ### 原理
快速排序的工作原理是通过 [分治](./divide-and-conquer.md) 的方式来将一个数组排序。
第三步中的序列已经分别有序且第一个序列中的数都小于第二个数,所以直接拼接起来就好了。
-### 性质
+### 线性找第 k 大的数
+
+找第 $k$ 大的数(K-th order statistic),最简单的方法是先排序,然后直接找到第 $k$ 大的位置的元素。这样做的时间复杂度是 $O(n\log n)$ ,对于这个问题来说很不划算。事实上存在时间复杂度 $O(n)$ 的解法。
+
+考虑快速排序的划分过程,在快速排序的「划分」结束后,数列 $A_{p} \cdots A_{r}$ 被分成了 $A_{p} \cdots A_{q}$ 和 $A_{q+1} \cdots A_{r}$ ,此时可以按照左边元素的个数( $q - p + 1$ )和 $k$ 的大小关系来判断是只在左边还是只在右边递归地求解。
+
+可以证明,在期望意义下,程序的时间复杂度为 $O(n)$ 。
+
+## 性质
+
+### 稳定性
+
+快速排序是一种不稳定的排序算法。
-- **稳定性:** 快速排序是一种不稳定的排序方法;
-- **时间复杂度:** 快速排序的最佳时间复杂度和平均时间复杂度为 $O(n\log n)$ ,最坏时间复杂度为 $O(n^2)$ 。然而,实践中几乎不可能达到最坏情况,且因为快速排序的内存访问遵循局部性原理,多数情况下快速排序的表现大幅优于堆排序等其他复杂度为 $O(n \log n)$ 的排序算法。[^ref1]
+### 时间复杂度
+
+快速排序的最优时间复杂度和平均时间复杂度为 $O(n\log n)$ ,最坏时间复杂度为 $O(n^2)$ 。
- 实践中几乎不可能达到最坏情况,且因为快速排序的内存访问遵循局部性原理,多数情况下快速排序的表现大幅优于堆排序等其他复杂度为 $O(n \log n)$ 的排序算法。[^ref1]
-
- 在选择 $m$ 的过程中,使用 [Median of Medians](https://en.wikipedia.org/wiki/Median_of_medians) 算法就可以保证最坏时间复杂度为 $O(n\log n)$ 。但是其过于复杂,实践中一般不使用。
-
- ## 代码实现
-
- ### C++[^ref2]
+ ### 实现(C++)[^ref2]
```cpp
struct Range {
内省排序将快速排序的最大递归深度限制为 $\lfloor \log_2n \rfloor$ ,超过限制时就转换为堆排序。这样既保留了快速排序内存访问的局部性,又可以防止快速排序在某些情况下性能退化为 $O(n^2)$ 。
-2000 年 6 月,SGI C++ STL 的 stl_algo.h 中 sort()函数的实现采用了内省排序算法。
+从 2000 年 6 月起,SGI C++ STL 的 `stl_algo.h` 中 `sort()` 函数的实现采用了内省排序算法。
- ## 三路快速排序
+ ## 线性找第 k 大的数
- 三路快速排序(英语:3-way Radix Quicksort)是快速排序和 [基数排序](radix-sort.md) 的混合。它的算法思想基于 [荷兰国旗问题](https://en.wikipedia.org/wiki/Dutch_national_flag_problem) 的解法。
+ 找第 $k$ 大的数(K-th order statistic),最简单的方法是先排序,然后直接找到第 $k$ 大的位置的元素。这样做的时间复杂度是 $O(n\log n)$ ,对于这个问题来说很不划算。
- 与原始的快速排序不同,三路快速排序在随机选取分界点 $m$ 后,将待排数列划分为三个部分:小于 $m$ 、等于 $m$ 以及大于 $m$ 。
+ 我们可以借助快速排序的思想解决这个问题。考虑快速排序的划分过程,在快速排序的「划分」结束后,数列 $A_{p} \cdots A_{r}$ 被分成了 $A_{p} \cdots A_{q}$ 和 $A_{q+1} \cdots A_{r}$ ,此时可以按照左边元素的个数( $q - p + 1$ )和 $k$ 的大小关系来判断是只在左边还是只在右边递归地求解。
- 三路快速排序在处理含有多个重复值的数组时,效率远高于原始快速排序。
-
- 三路快速排序的最优时间复杂度为 $O(n)$ 。
+ 可以证明,在期望意义下,程序的时间复杂度为 $O(n)$ 。
-### 实现(C++)
-
-```cpp
-// 模板的T参数表示元素的类型,此类型需要定义小于(<)运算
-template <typename T>
-// arr为查找范围数组,rk为需要查找的排名(从0开始),len为数组长度
-T find_kth_element(T arr[], int rk, const int len) {
- if (len <= 1) return arr[0];
- // 随机选择基准(pivot)
- const T pivot = arr[rand() % len];
- // i:当前操作的元素
- // j:第一个等于pivot的元素
- // k:第一个大于pivot的元素
- int i = 0, j = 0, k = len;
- // 完成一趟三路快排,将序列分为:小于pivot的元素 | 等于pivot的元素 |
- // 大于pivot的元素
- while (i < k) {
- if (arr[i] < pivot)
- swap(arr[i++], arr[j++]);
- else if (pivot < arr[i])
- swap(arr[i], arr[--k]);
- else
- i++;
- }
- // 根据要找的排名与两条分界线的位置,去不同的区间递归查找第k大的数
- // 如果小于pivot的元素个数比k多,则第k大的元素一定是一个小于pivot的元素
- if (rk < j) return find_kth_element(arr, rk, j);
- // 否则,如果小于pivot和等于pivot的元素加起来也没有k多,则第k大的元素一定是一个大于pivot的元素
- else if (rk >= k)
- return find_kth_element(arr + k, rk - k, len - k);
- // 否则,pivot就是第k大的元素
- return pivot;
-}
-```
-
## 参考资料与注释
[^ref1]: [C++ 性能榨汁机之局部性原理 - I'm Root lee !](http://irootlee.com/juicer_locality/)