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

凸包(Convex Hull)

计算几何 · Andrew 单调链 · O(n log n)

用橡皮筋套住一堆点

平面上撒了一堆点,凸包就是能把它们全都套住的最小凸多边形——想象拿一根橡皮筋绷开、松手让它收紧,最后贴住的那圈外围的点就是凸包的顶点,里面的点被包在内部。凸包是计算几何的地基:碰撞检测、最远点对、最小外接形状、路径规划的第一步,往往都从求凸包开始。

叉积:一次判断左转还是右转

核心工具是叉积。对三个点 O, A, B,cross(O,A,B) = (A−O)×(B−O) 的符号告诉你从 OA 到 OB 是往哪拐:大于 0 是左转(逆时针)、小于 0 是右转(顺时针)、等于 0 是三点共线。凸多边形(逆时针)的每个拐角都应该是左转;一旦出现右转,说明中间那个点是凹的,不该在凸包上。

单调链:下凸壳 + 上凸壳

Andrew 单调链先把所有点按 (x, y) 排序,然后分两趟。下凸壳从左到右扫描:维护一个栈,每加入一个点,就看栈顶两点到新点是不是非左转(叉积 ≤ 0)——是就弹栈(那个点是凹的),直到左转再把新点压入。上凸壳从右到左同样来一遍。两条链拼起来就是完整凸包,复杂度由排序决定,O(n log n)。

下面固定 7 个点。点「下一步」逐步看:琥珀是当前正在加入的点、红色是这一步因右转被弹出的点、灰色折线是当前的凸壳链。先构下凸壳、再构上凸壳,走到底两条链合成一圈绿色凸包多边形,中间的点 (3,3) 被留在里面。右侧代码随每一步同步高亮。

平面上 7 个点(已按 (x,y) 排序)。要用最紧的凸多边形套住它们——先从左到右构「下凸壳」,靠叉积判转向

1function convexHull(pts: Pt[]): Pt[] {
2 pts.sort((a, b) => a.x - b.x || a.y - b.y); // 按 (x,y) 排序
3 const cr = (o: Pt, a: Pt, b: Pt) => // 叉积 (A-O)×(B-O)
4 (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
5 const lower: Pt[] = [];
6 for (const p of pts) { // 下凸壳:左 → 右
7 while (lower.length >= 2 && cr(lower.at(-2)!, lower.at(-1)!, p) <= 0) lower.pop(); // 非左转弹栈
8 lower.push(p);
9 }
10 const upper: Pt[] = [];
11 for (const p of [...pts].reverse()) { // 上凸壳:右 → 左
12 while (upper.length >= 2 && cr(upper.at(-2)!, upper.at(-1)!, p) <= 0) upper.pop();
13 upper.push(p);
14 }
15 return [...lower.slice(0, -1), ...upper.slice(0, -1)]; // 下 + 上凸壳拼接
16}
点数7
阶段下凸壳
当前栈空
1 / 16

为什么单调链这么优雅

叉积定转向:cross(O,A,B) > 0 左转、< 0 右转、= 0 共线。
排序:按 (x, y) 排好,保证扫描单调、每个点只进出栈常数次。
弹栈:非左转(叉积 ≤ 0)就弹掉栈顶的凹点,栈里永远是凸的。
复杂度:排序 O(n log n) 主导,两趟扫描各 O(n)。

凸包只是计算几何的起点。有了它,旋转卡壳能在凸包上 O(n) 求出最远点对(直径)与最小外接矩形;Graham 扫描、礼品包裹(Jarvis 步进)是求凸包的其它经典思路;再往上还有半平面交、最近点对、扫描线等一整套「用叉积和排序处理几何」的方法。