计算几何(一):凸包问题(Convex Hull)
引言
首先介绍下什么是凸包?如下图:

在一个二维坐标系中,有若干点杂乱排列着,将最外层的点连接起来构成的凸多边型,它能包含给定的所有的点,这个多边形就是凸包。
实际上可以理解为用一个橡皮筋包含住所有给定点的形态。
凸包用最小的周长围住了给定的所有点。如果一个凹多边形围住了所有的点,它的周长一定不是最小,如下图。根据三角不等式,凸多边形在周长上一定是最优的。
凸包的求法
寻找凸包的算法有很多种,常用的求法有 Graham 扫描法和 Andrew 算法
Graham Scan 算法求凸包
Graham Scan 算法是一种十分简单高效的二维凸包算法,能够在 \(O(nlogn)\) 的时间内找到凸包。
Graham Scan 算法的做法是先确定一个起点(一般是最左边的点和最右边的点),然后一个个点扫过去,如果新加入的点和之前已经找到的点所构成的 "壳" 凸性没有变化,就继续扫,否则就把已经找到的最后一个点删去,再比较凸性,直到凸性不发生变化。分别扫描上下两个 "壳",合并在一起,凸包就找到了。这么说很抽象,我们看图来解释:

先找 "下壳",上下其实是一样的。首先加入两个点 A 和 B。

然后插入第三个点 C,并计算 \(\overrightarrow{AB}×\overrightarrow{BC}\) 的向量积,却发现向量积系数小于(等于)0,也就是说 \(\overrightarrow{BC}\) 在 \(\overrightarrow{AB}\) 的顺时针方向上。

于是删去 B 点。

按照这样的方法依次扫描,找完 "下壳" 后,再找 "上壳"。
关于扫描的顺序,有坐标序和极角序两种,本文采用前者。坐标序是比较两个点的 x 坐标,小的先被扫描(扫描上凸壳的时候反过来),如果两个点 x 坐标相同,那么就比较 y 坐标,同样的也是小的先被扫描(扫描上凸壳的时候也是反过来)。极角序使用 atan2 函数的返回值进行比较,读者可以自己尝试写下。
下面贴下代码:Graham Scan 算法
struct Point
{
double x, y;
Point operator-(Point & p)
{
Point t;
t.x = x - p.x;
t.y = y - p.y;
return t;
}
double cross(Point p) // 向量叉积
{
return x * p.y - p.x * y;
}
};
bool cmp(Point & p1, Point & p2)
{
if (p1.x != p2.x)
return p1.x < p2.x;
return p1.y < p2.y;
}
Point point[1005]; // 无序点
int convex[1005]; // 保存组成凸包的点的下标
int n; // 坐标系的无序点的个数
int GetConvexHull()
{
sort(point, point + n, cmp);
int temp;
int total = 0;
for (int i = 0; i < n; i++) // 下凸包
{
while (total > 1 &&
(point[convex[total - 1]] - point[convex[total - 2]]).cross(point[i] - point[convex[total - 1]]) <= 0)
total--;
convex[total++] = i;
}
temp = total;
for (int i = n - 2; i >= 0; i--) // 上凸包
{
while (total > temp &&
(point[convex[total - 1]] - point[convex[total - 2]]).cross(point[i] - point[convex[total - 1]]) <= 0)
total--;
convex[total++] = i;
}
return total - 1; // 返回组成凸包的点的个数,实际上多了一个,就是起点,所以组成凸包的点个数是 total - 1
}
Andrew 算法求凸包
首先把所有点以横坐标为第一关键字,纵坐标为第二关键字排序。
显然排序后最小的元素和最大的元素一定在凸包上。而且因为是凸多边形,我们如果从一个点出发逆时针走,轨迹总是“左拐”的,一旦出现右拐,就说明这一段不在凸包上。因此我们可以用一个单调栈来维护上下凸壳。
因为从左向右看,上下凸壳所旋转的方向不同,为了让单调栈起作用,我们首先 升序枚举 求出下凸壳,然后 降序 求出上凸壳。
求凸壳时,一旦发现即将进栈的点( \(P\) )和栈顶的两个点( \(S_1,S_2\) ,其中 \(S_1\) 为栈顶)行进的方向向右旋转,即叉积小于 \(0\) : \(\overrightarrow{S_2S_1}\times \overrightarrow{S_1P}<0\) ,则弹出栈顶,回到上一步,继续检测,直到 \(\overrightarrow{S_2S_1}\times \overrightarrow{S_1P}\ge 0\) 或者栈内仅剩一个元素为止。
通常情况下不需要保留位于凸包边上的点,因此上面一段中 \(\overrightarrow{S_2S_1}\times \overrightarrow{S_1P}<0\) 这个条件中的“ \(<\) ”可以视情况改为 \(\le\) ,同时后面一个条件应改为 \(>\) 。
代码实现
// stk[]是整型,存的是下标
// p[]存储向量或点
tp = 0; //初始化栈
std::sort(p + 1, p + 1 + n); //对点进行排序
stk[++tp] = 1;
//栈内添加第一个元素,且不更新used,使得1在最后封闭凸包时也对单调栈更新
for (int i = 2; i <= n; ++i) {
while (tp >= 2 //下一行*被重载为叉积
&& (p[stk[tp]] - p[stk[tp - 1]]) * (p[i] - p[stk[tp]]) <= 0)
used[stk[tp--]] = 0;
used[i] = 1; // used表示在凸壳上
stk[++tp] = i;
}
int tmp = tp; // tmp表示下凸壳大小
for (int i = n - 1; i > 0; --i)
if (!used[i]) {
// ↓求上凸壳时不影响下凸壳
while (tp > tmp && (p[stk[tp]] - p[stk[tp - 1]]) * (p[i] - p[stk[tp]]) <= 0)
used[stk[tp--]] = 0;
used[i] = 1;
stk[++tp] = i;
}
for (int i = 1; i <= tp; ++i) //复制到新数组中去
h[i] = p[stk[i]];
int ans = tp - 1;
根据上面的代码,最后凸包上有 \(ans\) 个元素(额外存储了 \(1\) 号点,因此 \(h\) 数组中有 \(ans+1\) 个元素),并且按逆时针方向排序。周长就是
\]
参考
计算几何(一):凸包问题(Convex Hull)的更多相关文章
- 凸包(Convex Hull)构造算法——Graham扫描法
凸包(Convex Hull) 在图形学中,凸包是一个非常重要的概念.简明的说,在平面中给出N个点,找出一个由其中某些点作为顶点组成的凸多边形,恰好能围住所有的N个点. 这十分像是在一块木板上钉了N个 ...
- OpenCV入门之寻找图像的凸包(convex hull)
介绍 凸包(Convex Hull)是一个计算几何(图形学)中的概念,它的严格的数学定义为:在一个向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包. 在图像处理过程中,我们 ...
- 2D Convex Hulls and Extreme Points( Convex Hull Algorithms) CGAL 4.13 -User Manual
1 Introduction A subset S⊆R2 is convex if for any two points p and q in the set the line segment wit ...
- Monotone Chain Convex Hull(单调链凸包)
Monotone Chain Convex Hull(单调链凸包)算法伪代码: //输入:一个在平面上的点集P //点集 P 按 先x后y 的递增排序 //m 表示共a[i=0...m]个点,ans为 ...
- opencv::凸包-Convex Hull
概念介绍 什么是凸包(Convex Hull),在一个多变形边缘或者内部任意两个点的连线都包含在多边形边界或者内部. 正式定义:包含点集合S中所有点的最小凸多边形称为凸包 Graham扫描算法 首先选 ...
- convex hull
1 什么是convex hull 就是凸包,是计算几何中的一个概念,计算几何是计算机图形学的基础之一. 对于二维平面来说是这样的:对于二维平面上的点集,凸包是位于最外层的点构成的包围其它所有的点的凸多 ...
- Convex Hull 实现理论+自制Python代码
Convex Hull 概述 计算n维欧式空间散点集的凸包,有很多的方法.但是如果要实现快速运算则其难点在于:如何快速判断散点集的成员是否是在凸集的内部.如果可以简化判断的运算过程,则可以极大简化迭代 ...
- hrbustoj 1318:蛋疼的蚂蚁(计算几何,凸包变种,叉积应用)
蛋疼的蚂蚁 Time Limit: 1000 MS Memory Limit: 65536 K Total Submit: 39(22 users) Total Accepted: 26 ...
- poj 1696:Space Ant(计算几何,凸包变种,极角排序)
Space Ant Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 2876 Accepted: 1839 Descrip ...
- Convex Hull | Set 1
Given a set of points in the plane. the convex hull of the set is the smallest convex polygon that c ...
随机推荐
- 区块链入门到实战(30)之Solidity – 基础语法
一个 Solidity 源文件可以包含任意数量的合约定义.import指令和pragma指令. 让我们从一个简单的 Solidity 源程序开始.下面是一个 Solidity 源文件的例子: prag ...
- Docker 镜像构建之 Dockerfile
在 Docker 中创建镜像最常用的方式,就是使用 Dockerfile.Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明.官方文档:https://d ...
- Qt 让Label显示图片并把图片居中
Qt 让Label显示图片并把图片居中 QPixmap image("./13.jpg"); QPixmap fitpixmap=image.scaled(ui->lab ...
- Android自动化测试,5个必备的测试框架
Appium Appium是一个开源的移动测试工具,支持iOS和Android,它可以用来测试任何类型的移动应用(原生.网络和混合).作为一个跨平台的工具,你可以在不同的平台上运行相同的测试.为了实现 ...
- 微信小程序入门教程
首先请看demo 很简单的静态js就可以实现一款小程序开发. js.json.html.css四个核心文件 序言 开始开发应用号之前,先看看官方公布的「小程序」教程吧!(以下内容来自微信官方公布的「小 ...
- App性能测试前需要了解的内存原理
这两天在研究性能中内存方面的一块,网上也零散看了挺多文章,写得很细但是感觉不够整体,所以这篇算是总结一下吧,当个复习资料. 那么这里个人分为两个大部分,第一部分应用内的内存管理,主要是oom的理解,G ...
- java返回树形结构的正确姿势
业务场景 通常我们前端需要一个树形的导航菜单或者分类菜单,如后台权限管理中的权限树,亦或者下面例子中商城系统的商品分类多级菜单(一般为三级菜单) 数据库设计 数据库设计,采用parentId来指向自己 ...
- HDU - 1005 -Number Sequence(矩阵快速幂系数变式)
A number sequence is defined as follows: f(1) = 1, f(2) = 1, f(n) = (A * f(n - 1) + B * f(n - 2)) m ...
- [BUUOJ记录] [BJDCTF 2nd]文件探测
感觉算是这次比赛里面综合性很强的一道题了,主要考察SSRF.PHP伪协议包含.挖掘逻辑漏洞和一个小tirck.委屈的是第一天晚上就做到了最后一步,想到了SESSION置空即可绕过,但是最后读Flag姿 ...
- 《神经网络的梯度推导与代码验证》之vanilla RNN前向和反向传播的代码验证
在<神经网络的梯度推导与代码验证>之vanilla RNN的前向传播和反向梯度推导中,我们学习了vanilla RNN的前向传播和反向梯度求导,但知识仍停留在纸面.本篇章将基于深度学习框架 ...
