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
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

计数排序 Counting Sort

非比较排序 · 用空间换线性时间

什么是计数排序

前面的排序都靠两两比较决定顺序,理论下限是 O(n log n)。计数排序换了个思路:根本不比较——如果知道数据都是小范围内的整数,那就数一数每个值出现了几次,再按值从小到大把它们「倒」回去,天然就有序了。用「统计 + 回填」代替「比较 + 交换」,时间可以做到线性。

它怎么做

为值域里每个可能的值准备一个「桶」(计数格)。第一遍扫描:读到值 v,就给桶 v 计数 +1(一个格子 = 出现一次,「萝卜一个坑」)。第二遍:按桶从小到大,桶里有几个就往结果里写几个。点「下一步」看 [3, 1, 4, 1, 6, 2, 3, 6, 4, 1]:下方计数桶轨按「值」排开(1~6),读一个数对应桶就高亮 +1;留意值 5 从没出现,是个空桶。

3
1
4
1
6
2
3
6
4
1
0
1
0
2
1
3
0
4
0
5
0
6

读 a[0]=3 → 桶「3」+1(现 1 颗)

1function countingSort(a: number[]): number[] {
2 const min = Math.min(...a), max = Math.max(...a);
3 const count = new Array(max - min + 1).fill(0);
4 for (let i = 0; i < a.length; i++) count[a[i] - min]++;
5 let w = 0;
6 for (let v = 0; v < count.length; v++) {
7 while (count[v] > 0) {
8 a[w++] = v + min;
9 count[v]--;
10 }
11 }
12 return a;
13}
n10
min1
max6
k6
阶段计数
i0
v-
w-
1 / 27

复杂度与适用

时间 O(n + k)(n 个元素 + k 个桶)、额外空间 O(k)、稳定。它只适用于取值范围 k 不太大的整数(或可映射为整数的键)——k 远大于 n 时(如排 10 个 32 位整数)就得不偿失。它是「非比较线性排序」三件套的基础,也是基数排序每一位所用的子过程。

值域太大怎么办?基数排序按位分批计数(每位只需 10 个桶),把大值域拆成几轮小计数。
值分布不均怎么办?桶排序按值域段分桶、桶内再排序。
三者同属非比较线性排序,桶的「粒度」不同。

计数是线性排序的起点:往上一层是基数排序(按位计数)、桶排序(按段分桶)——对照三页,能看清「用什么当桶」这条主线。