算法解析:felipernb/algorithms.js中的Graham Scan凸包算法
2025-07-09 04:46:04作者:姚月梅Lane
什么是凸包?
在计算几何中,凸包(Convex Hull)是指包含给定点集的最小凸多边形。想象一下,用橡皮筋套住一组钉子,橡皮筋最终形成的形状就是这些钉子的凸包。凸包在计算机图形学、模式识别、地理信息系统等领域有着广泛应用。
Graham Scan算法概述
Graham Scan是一种经典的凸包计算算法,由Ronald Graham在1972年提出。该算法的时间复杂度为O(n log n),其中n是输入点集的大小。felipernb/algorithms.js项目中的实现遵循了标准的Graham Scan算法流程。
算法实现解析
1. 预处理阶段
预处理阶段主要完成两个任务:
- 寻找基准点(pivot):选择y坐标最小的点作为基准点(如果y坐标相同,则选择x坐标较小的点)。这个点必定在凸包上。
let pivot = p[0];
for (let i = 1; i < p.length; i++) {
if (pivot.y > p[i].y || (pivot.y === p[i].y && pivot.x > p[i].x)) {
pivot = p[i];
}
}
- 按极角排序:以基准点为原点,按照其余点的极角(与基准点形成的向量与y轴的夹角)进行排序。如果极角相同,则距离基准点较近的点排在前面。
p.sort((a, b) => {
const area = vectorOp.parallelogramArea(pivot, a, b);
if (Math.abs(area) < 1e-6) { // 处理共线情况
return (
vectorOp.length(vectorOp.newVector(pivot, a)) -
vectorOp.length(vectorOp.newVector(pivot, b))
);
}
return -area;
});
2. 扫描构建凸包
预处理完成后,算法使用栈结构来构建凸包:
- 初始时将前两个点压入栈
- 对于每个后续点,检查它是否构成"右转"(非逆时针方向)
- 如果是右转,则弹出栈顶元素,直到不再右转为止
- 将当前点压入栈
const convexHull = [p[0], p[1]];
for (let i = 2; i < p.length; i++) {
let j = convexHull.length;
while (
j >= 2 &&
!vectorOp.isCounterClockwise(convexHull[j - 2], convexHull[j - 1], p[i])
) {
convexHull.pop();
j--;
}
convexHull.push(p[i]);
}
关键几何运算
算法依赖于几个重要的几何运算,这些运算在vector_operations2d.js
中实现:
- 平行四边形面积计算:用于判断三点是否共线以及它们的相对方向
- 向量长度计算:用于处理共线点时确定点的顺序
- 逆时针方向判断:核心的凸包构建判断条件
算法特性
- 时间复杂度:预处理排序O(n log n),扫描阶段O(n),总体O(n log n)
- 空间复杂度:O(n),主要用于存储排序后的点和凸包结果
- 特殊情况处理:正确处理了共线点的情况,只保留最远的两个点
实际应用示例
假设我们有一组点坐标,想要计算它们的凸包:
const points = [
{x: 0, y: 0},
{x: 1, y: 1},
{x: 2, y: 2},
{x: 0, y: 2},
{x: 2, y: 0}
];
const convexHull = grahamScan(points);
// 结果将包含构成凸包的点
总结
felipernb/algorithms.js中的Graham Scan实现是一个高效、清晰的凸包计算算法实现。它遵循了标准的算法流程,并正确处理了各种边界情况。理解这个实现不仅有助于掌握凸包计算,也是学习计算几何中经典算法设计思想的良好范例。