V

算法可视化

ZHEN
分享到微博分享到 XGitHub 仓库个人主页
学习工具算法复杂度速查算法学习路径
数据结构数组链表栈队列树堆哈希表图字典树并查集LRU 缓存跳表线段树B+ 树布隆过滤器树状数组
经典排序算法冒泡排序鸡尾酒排序双调排序选择排序插入排序二分插入排序希尔排序归并排序自顶向下归并快速排序三路快排双轴快排堆排序计数排序基数排序桶排序
图算法Dijkstra 最短路Kruskal 最小生成树Prim 最小生成树Bellman-Ford 最短路拓扑排序Floyd 多源最短路强连通分量2-SAT最大流二分图匹配LCA 倍增欧拉路径
动态规划编辑距离0-1 背包完全背包最长公共子序列最长递增子序列硬币找零方案数石子合并旅行商 TSP树形 DP数位 DP换根 DP
回溯与搜索N 皇后子集生成全排列组合总和迷宫寻路岛屿数量单词搜索数独A* 寻路
字符串KMP 字符串匹配Rabin-KarpBoyer-MooreManacher后缀数组LCP / height 数组AC 自动机Z 函数
数学与数论埃氏筛线性筛欧几里得算法快速幂扩展欧几里得中国剩余定理欧拉函数米勒-拉宾FFTPollard's Rho
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

三路快排 3-way Quicksort

交换排序 · 快排变体 · 荷兰国旗划分

普通快排的软肋

普通快排每趟只把数组分成「< pivot」和「≥ pivot」两段,pivot 自己归位。可一旦数组里有大量重复元素,等于 pivot 的那些值会被反复塞进同一侧、一遍遍参与后续划分——递归变深,最坏退化到 O(n²)。极端情况「全部相等」时,普通快排慢得离谱。

三路快排怎么做(荷兰国旗划分)

三路快排把一趟划分从「两段」升级成三段:< pivot / == pivot / > pivot。等于 pivot 的元素一次性集中到中段、立即钉死,再也不参与递归——重复元素越多,省得越狠。这个「把一组数按某个基准劈成三段」的过程,就是经典的荷兰国旗问题(Dutch National Flag)。

做法是三个指针 lt / i / gt:lt 是「小于区」的右边界、gt 是「大于区」的左边界、i 是扫描游标。扫描时看 a[i] 和 pivot 的关系——小于就和 lt 交换、lt 与 i 同时右移;大于就和 gt 交换、gt 左移(i 不动,因为换过来的值还没查过);等于就只把 i 右移、留在中段。当 i 越过 gt,三段就分好了。

下面固定 8 个数 [5, 3, 8, 3, 5, 8, 3, 5](只有 3、5、8 三种值、大量重复)。点「下一步」看首趟划分(pivot=5)怎么把它们一趟分成 [3,3,3 | 5,5,5 | 8,8]——中间那段 5 直接钉死、不再递归,只剩左右两小段要处理。右侧区间栈显示「还有哪些段待划分」。

5
3
8
3
5
8
3
5
区间栈 · 每格 = 一段待排序子数组 a[lo..hi](栈顶先弹出分区)
栈空 → 全部就位

弹出区间 [0,7]

1function quickSort3way(a: number[]): number[] {
2 const stack: [number, number][] = [[0, a.length - 1]];
3 while (stack.length > 0) {
4 const [lo, hi] = stack.pop()!;
5 const pivot = a[lo];
6 let lt = lo, i = lo, gt = hi;
7 while (i <= gt) {
8 if (a[i] < pivot) {
9 [a[lt], a[i]] = [a[i], a[lt]];
10 lt++; i++;
11 } else if (a[i] > pivot) {
12 [a[i], a[gt]] = [a[gt], a[i]];
13 gt--;
14 } else {
15 i++;
16 }
17 }
18 if (gt + 1 < hi) stack.push([gt + 1, hi]);
19 if (lt - 1 > lo) stack.push([lo, lt - 1]);
20 }
21 return a;
22}
n8
栈深0
lo0
hi7
pivot-
lt-
i-
gt-
1 / 36

复杂度与适用

和普通快排一样,平均 O(n log n)、原地、不稳定。但在有大量重复键的数据上,三路快排把「等于 pivot」的元素一次清掉,递归规模骤减——重复种类越少越接近 O(n)。所以它是处理「键的取值范围小、重复多」场景的利器(如按枚举字段、评分、类别排序)。

普通快排:两段划分(< / ≥),重复多时等值元素反复参与递归 → 退化。
三路快排:三段划分(< / == / >),等值元素一次钉死 → 重复越多越快。
它正是 Java Arrays.sort 早期、以及不少标准库对偏斜数据的应对思路。

想先复习普通快排的「基准 + 区间栈」是怎么回事,可回看快速排序页——三路快排正是在它基础上,把划分从两段改成三段。