【计算几何】浅谈凸包Andrew算法
前置知识
引文
这样一个问题,
有许多个杆子,需要用绳子围住所有的杆子,然鹅没有很多的绳子,求最短需要多少绳子。
整个图大概是这样的,
正文
我们要如何解决这题呢?不难想出,最优解法应该是这样的,
而这个图中隐藏着解决这道题的奥秘。
特别的这个图被我们称为凸包。
首先我们可以把它分割成两个图,分为上壳与下壳
分析一下,我们可以发现,在上壳中,编号从大到小,都是向右转,下壳相同。
可以推测个结论,在上壳中,如果一个节点需要左转,这次时必定不是最优的,下壳相同,
怎么样才能改成最优的呢?很简单,让他变成向反方向转:将之前的节点一个个删除,直到达到目的为止。
凸包步骤
首先,将节点按 第一关键字, 第二关键字排序,
然后枚举上壳:现将节点 与节点 加入凸包,并加入 ,
尝试连接 ,然而由于 在 的左方向,并不是最短的,所以并不可行,
将 删去凸包中,依次连接 、 、 ,
不可行,将 删去;
这样上壳就枚举完了,便开始枚举下壳。
下壳是从编号大向编号小的枚举,
依次加入 、 、 、
然而 在 的左边,自然并非最优,
删掉 ,连入 ,
发现 在 的左边,于是删掉 ,连入 。( 显然连不了)
然后凸包便成功生成了出来11!
补充
还有最后一个问题,我们该如何判断一个点在直线的方向?
将该节点与直线首尾相接,当这两个直线的叉积大于 时,点在左方;
小于 时,点在右方;
当其等于 时,点与直线共线。
代码
inline int Andrew(Point *p, int n, Point *Poly) { // 求凸包
int used[N] = {0}; // 标记每个数字是否使用过
sort(p + 1, p + n + 1, [](Point a, Point b) { // 排序节点
return a.x < b.x || (a.x == b.x && a.y < b.y);
});
int cnt = 3;
Poly[1] = p[1], Poly[2] = p[2]; // 初始时加入1号和2号节点
for (int i = 3; i <= n; ++i) {
if (cnt > 1 && Cross(Poly[cnt] - Poly[cnt - 1], Poly[i] - Poly[cnt] <= 0)) { // 判断是否在左边
-- cnt; // 删除此节点
used[Poly[cnt + 1]] = 0;
}
used[i] = 1; // 标记节点
Poly[++ cnt] = p[i]; // 加入凸包
}
int k = cnt; // 上壳的起点
for (int i = n; i >= 1; --i) {
if (used[i]) { // 已经加入凸包
continue;
}
while (cnt > k && Cross(Poly[cnt] - Poly[cnt - 1], Poly[i] - Poly[cnt]) <= 0)) {
-- cnt;
used[Poly[cnt + 1]] = 0;
}
used[i] = 1;
Poly[++ cnt] = p[i];
}
Poly[++ cnt] = 1; // 便于计算周长
return cnt;
}
本文作者:SenGYiの小屋
本文链接:https://www.cnblogs.com/Sengyi/p/17046518.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步