目录
一、什么是凸包(Convex Hull)?
把一些点钉在木板上,然后用一根橡皮筋把它们套起来,松开后橡皮筋形成的形状就是凸包。
给定平面上一组点,凸包就是一个凸多边形,它刚好把所有点包围在内部或边界上,而且是面积最小的那种凸多边形。
二、常见的几种算法
-
Graham Scan
-
Andrew 算法
| 项目 | Graham Scan | Andrew 算法 |
|---|---|---|
| 复杂度 | O(n log n) | O(n log n) |
| 是否极角排序 | ✅ 是 | ❌ 否 |
| 代码结构 | 稍复杂 | 更直观 |
| 适合手写 | ✅ 比较好 | ✅ 非常好 |
三、用到的数学知识
1、叉积(Cross Product)
在二维空间中,叉积通常被定义为两个向量的“伪叉积”,其结果是一个标量(而非三维空间中的向量),可用于判断向量方向或计算平行四边形面积。
数学公式:设向量 a ⃗ = ( x 1 , y 1 ) \vec{a} = (x_1, y_1) a=(x1,y1),向量 b ⃗ = ( x 2 , y 2 ) \vec{b} = (x_2, y_2) b=(x2,y2),则二者的叉积为:
a ⃗ × b ⃗ = x 1 y 2 − x 2 y 1 \vec{a} \times \vec{b} = x_1 y_2 - x_2 y_1 a×b=x1y2−x2y1
简单讲解:
- 结果的正负表示两向量的相对方向:正号表示 a ⃗ \vec{a} a 在 b ⃗ \vec{b} b 顺时针方向,负号则相反。
- 结果的绝对值等于两向量构成的平行四边形面积(也等于以两向量为邻边的三角形面积的 2 倍)。
2、欧几里得距离(Euclidean Distance)
二维空间中,欧几里得距离即两点之间的直线距离,是最常用的距离度量方式。
数学公式:设平面上两点 A ( x 1 , y 1 ) A(x_1, y_1) A(x1,y1) 和 B ( x 2 , y 2 ) B(x_2, y_2) B(x2,y2),则两点间的欧几里得距离为:
d ( A , B ) = ( x 2 − x 1 ) 2 + ( y 2 − y 1 ) 2 d(A, B) = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2} d(A,B)=(x2−x1)2+(y2−y1)2
三、Graham Scan
📌步骤一:找最低点
-
找到 y 最小的点,若 y 相同,取 x 最小的。
-
这个点作为 起点 P0。
📌步骤二:按极角排序
- 把其他点按相对于 P0 的 极角(从 x 轴逆时针旋转的角度)排序。
📌步骤三:遍历所有点,构建凸包
-
用一个栈来存凸包点。
-
每加一个点时,检查是否左转:
-
若不是左转(即右转或共线),就 弹出栈顶;
-
一直这样弹,直到形成左转。
-
Java 代码实现
三个点一起看,就很好理解
import java.util.*;
public class ConvexHull {
static class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
public static List<Point> convexHull(Point[] points) {
if (points.length <= 1) return Arrays.asList(points);
// 1. 找到最低点
Point start = points[0];
for (Point p : points) {
if (p.y < start.y || (p.y == start.y && p.x < start.x)) {
start = p;
}
}
// 2. 按极角排序(从小到大)
// 正数,b在前
Arrays.sort(points, (a, b) -> {
int cp = cross(start, a, b);
if (cp == 0) return dist(start, a) - dist(start, b);
return -cp;
});
// 3. 构建凸包
Stack<Point> stack = new Stack<>();
for (Point p : points) {
while (stack.size() >= 2 &&
cross(stack.get(stack.size() - 2), stack.peek(), p) <= 0) {
stack.pop(); // 右转 or 共线
}
stack.push(p);
}
return new ArrayList<>(stack);
}
// 计算的是向量 ab × ac 的二维伪叉积
// > 0:左转(保持凸性,保留)
// = 0:共线(可能去掉)
// < 0:右转(破坏凸性,需要弹出)
static int cross(Point a, Point b, Point c) {
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}
// 由于只需要正负,计算两点之间的距离的平方
static int dist(Point a, Point b) {
return (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y);
}
}
推荐观看:Graham算法构造凸包(b站上的视频,有图示)
四、Andrew 算法(也叫Monotone Chain)
📌步骤一:按照 x 再 y 排序
Arrays.sort(points, (a, b) -> a.x == b.x ? a.y - b.y : a.x - b.x);
从左到右,从下到上排。
📌步骤二:构造下凸包
Stack<Point> lower = new Stack<>();
// 构建下凸壳
for (Point p : points) {
while (lower.size() >= 2 &&
cross(lower.get(lower.size() - 2), lower.peek(), p) <= 0) {
lower.pop();
}
lower.push(p);
}
📌步骤三:构造上凸包(注意从后往前)
Stack<Point> upper = new Stack<>();
// 构建上凸壳
for (int i = n - 1; i >= 0; i--) {
Point p = points[i];
while (upper.size() >= 2 &&
cross(upper.get(upper.size() - 2), upper.peek(), p) <= 0) {
upper.pop();
}
upper.push(p);
}
📌步骤四:拼接上下凸包
lower.pop(); // 去掉最高点
upper.pop(); // 去掉最低点
while (!upper.isEmpty()) lower.push(upper.pop());
Java 代码实现
import java.util.*;
public class ConvexHull {
static class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
public static List<Point> convexHull(Point[] points) {
int n = points.length;
if (n <= 1) return Arrays.asList(points);
// 1. 先按照 x 升序,若 x 相同则按 y 升序排列
Arrays.sort(points, (a, b) -> a.x == b.x ? a.y - b.y : a.x - b.x);
Stack<Point> lower = new Stack<>();
// 构建下凸壳
for (Point p : points) {
while (lower.size() >= 2 &&
cross(lower.get(lower.size() - 2), lower.peek(), p) <= 0) {
lower.pop();
}
lower.push(p);
}
Stack<Point> upper = new Stack<>();
// 构建上凸壳
for (int i = n - 1; i >= 0; i--) {
Point p = points[i];
while (upper.size() >= 2 &&
cross(upper.get(upper.size() - 2), upper.peek(), p) <= 0) {
upper.pop();
}
upper.push(p);
}
// 合并上下壳(去除重复的首尾点)
lower.pop(); // 去掉最高点
upper.pop(); // 去掉最低点
while (!upper.isEmpty()) lower.push(upper.pop());
return new ArrayList<>(lower);
}
// 计算叉积:向量 ab × ac
static int cross(Point a, Point b, Point c) {
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}
}
推荐观看:凸包 Andrew算法(b站上的视频,有图示)
五、练习题建议(LeetCode / 牛客网)
-
LeetCode 587. 安装栅栏
-
牛客网 / 洛谷:凸包模板题

3087

被折叠的 条评论
为什么被折叠?



