首页
/ 算法解析:felipernb/algorithms.js中的Graham Scan凸包算法

算法解析: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. 预处理阶段

预处理阶段主要完成两个任务:

  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];
  }
}
  1. 按极角排序:以基准点为原点,按照其余点的极角(与基准点形成的向量与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. 扫描构建凸包

预处理完成后,算法使用栈结构来构建凸包:

  1. 初始时将前两个点压入栈
  2. 对于每个后续点,检查它是否构成"右转"(非逆时针方向)
  3. 如果是右转,则弹出栈顶元素,直到不再右转为止
  4. 将当前点压入栈
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中实现:

  1. 平行四边形面积计算:用于判断三点是否共线以及它们的相对方向
  2. 向量长度计算:用于处理共线点时确定点的顺序
  3. 逆时针方向判断:核心的凸包构建判断条件

算法特性

  1. 时间复杂度:预处理排序O(n log n),扫描阶段O(n),总体O(n log n)
  2. 空间复杂度:O(n),主要用于存储排序后的点和凸包结果
  3. 特殊情况处理:正确处理了共线点的情况,只保留最远的两个点

实际应用示例

假设我们有一组点坐标,想要计算它们的凸包:

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实现是一个高效、清晰的凸包计算算法实现。它遵循了标准的算法流程,并正确处理了各种边界情况。理解这个实现不仅有助于掌握凸包计算,也是学习计算几何中经典算法设计思想的良好范例。