凸包

计算几何 / convex-hull

本地源文件:docs/geometry__convex-hull.md

凸包

二维凸包

定义

凸多边形

凸多边形是指所有内角大小都在 [0,𝜋][0,π] 范围内的 简单多边形

凸包

在平面上能包含所有给定点的最小凸多边形叫做凸包.

其定义为:对于给定集合 𝑋X,所有包含 𝑋X 的凸集的交集 𝑆S 被称为 𝑋X凸包

实际上可以理解为用一个橡皮筋包含住所有给定点的形态.

凸包用最小的周长围住了给定的所有点.如果一个凹多边形围住了所有的点,它的周长一定不是最小,如下图.根据三角不等式,凸多边形在周长上一定是最优的.

Andrew 算法求凸包

常用的求法有 Graham 扫描法和 Andrew 算法,这里主要介绍 Andrew 算法.

性质

该算法的时间复杂度为 𝑂(𝑛log⁡𝑛)O(nlog⁡n),其中 𝑛n 为待求凸包点集的大小,复杂度的瓶颈在于对所有点坐标的双关键字排序.

过程

首先把所有点以横坐标为第一关键字,纵坐标为第二关键字排序.

显然排序后最小的元素和最大的元素一定在凸包上.而且因为是凸多边形,我们如果从一个点出发逆时针走,轨迹总是「左拐」的,一旦出现右拐,就说明这一段不在凸包上.因此我们可以用一个单调栈来维护上下凸壳.

因为从左向右看,上下凸壳所旋转的方向不同,为了让单调栈起作用,我们首先 升序枚举 求出下凸壳,然后 降序 求出上凸壳.

求凸壳时,一旦发现即将进栈的点(𝑃P)和栈顶的两个点(𝑆1,𝑆2S1,S2,其中 𝑆1S1 为栈顶)行进的方向向右旋转,即叉积小于 00:←←←←←←→𝑆2𝑆1 ×←←←←←→𝑆1𝑃 <0S2S1→×S1P→<0,则弹出栈顶,回到上一步,继续检测,直到 ←←←←←←→𝑆2𝑆1 ×←←←←←→𝑆1𝑃 ≥0S2S1→×S1P→≥0 或者栈内仅剩一个元素为止.

通常情况下不需要保留位于凸包边上的点,因此上面一段中 ←←←←←←→𝑆2𝑆1 ×←←←←←→𝑆1𝑃 <0S2S1→×S1P→<0 这个条件中的「<<」可以视情况改为 ≤≤,同时后面一个条件应改为 >>

Andrew

实现

代码实现

C++Python

---|---

---|---

根据上面的代码,最后凸包上有 𝑎𝑛𝑠ans 个元素(额外存储了 11 号点,因此 ℎh 数组中有 𝑎𝑛𝑠 +1ans+1 个元素),并且按逆时针方向排序.周长就是

𝑎𝑛𝑠∑𝑖=1∣←←←←←←←←→ℎ𝑖ℎ𝑖+1∣∑i=1ans|hihi+1→|

Graham 扫描法

性质

与 Andrew 算法相同,Graham 扫描法的时间复杂度为 𝑂(𝑛log⁡𝑛)O(nlog⁡n),复杂度瓶颈也在于对所有点排序.

过程

首先找到所有点中,纵坐标最小的一个点 𝑃P.根据凸包的定义我们知道,这个点一定在凸包上.然后将所有的点以相对于点 P 的极角大小为关键字进行排序.

和 Andrew 算法类似地,我们考虑从点 𝑃P 出发,在凸包上逆时针走,那么我们经过的所有节点一定都是「左拐」的.形式化地说,对于凸包逆时针方向上任意连续经过的三个点 𝑃1,𝑃2,𝑃3P1,P2,P3,一定满足 ←←←←←←→𝑃1𝑃2 ×←←←←←←→𝑃2𝑃3 ≥0P1P2→×P2P3→≥0

新建一个栈用于存储凸包的信息,先将 𝑃P 压入栈中,然后按照极角序依次尝试加入每一个点.如果进栈的点 𝑃0P0 和栈顶的两个点 𝑃1,𝑃2P1,P2(其中 𝑃1P1 为栈顶)行进的方向「右拐」了,那么就弹出栈顶的 𝑃1P1,不断重复上述过程直至进栈的点与栈顶的两个点满足条件,或者栈中仅剩下一个元素,再将 𝑃0P0 压入栈中.

代码实现

---|---

## 闵可夫斯基和

### 定义

点集 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 和点集 𝑄Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的闵可夫斯基和 𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 定义为 𝑃 +𝑄 ={𝑎 +𝑏|𝑎 ∈𝑃,𝑏 ∈𝑄}P+Q={a+b|a∈P,b∈Q}![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),即把点集 𝑄Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中的每个点看做一个向量,将点集 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中每个点沿这些向量平移,最终得到的结果的集合就是点集 𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).此处仅讨论 **凸包** 的闵可夫斯基和.

例如:对于点集 𝑃 ={(0,0),( −3,3),(2,1)}P={(0,0),(−3,3),(2,1)}![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 和 点集 𝑄 ={(0,0),( −1,3),(1,4),(2,2)}Q={(0,0),(−1,3),(1,4),(2,2)}![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),

![](./images/convex-hull1.svg)

将 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 沿 𝑄Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的每个向量平移:

![](./images/convex-hull2.svg)

不难发现新图形也是一个 **凸包** :

![](./images/convex-hull3.svg)

### 性质

  1. 若点集合 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),𝑄Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 为凸集,则其闵可夫斯基和 𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 也是凸集.

证明

设 𝑒,𝑓 ∈𝑃 +𝑄e,f∈P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),有 𝑎,𝑏 ∈𝑃a,b∈P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),𝑐,𝑑 ∈𝑄c,d∈Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 且 𝑒 =𝑎 +𝑐,𝑓 =𝑏 +𝑑e=a+c,f=b+d![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),则对任意 𝑡 ∈[0,1]t∈[0,1]![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 均有:

𝑡𝑒+(1−𝑡)𝑓=𝑡(𝑎+𝑐)+(1−𝑡)(𝑏+𝑑)=(𝑡𝑎+(1−𝑡)𝑏)+(𝑡𝑐+(1−𝑡)𝑑)∈𝑃+𝑄.te+(1−t)f=t(a+c)+(1−t)(b+d)=(ta+(1−t)b)+(tc+(1−t)d)∈P+Q.![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)

证毕.

     1. 若点集 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),𝑄Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 为凸集,则其闵可夫斯基和 𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的边集是由凸集 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),𝑄Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的边按极角排序后连接的结果.
证明

不妨假设凸集 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中任意一条边的斜率与 𝑄Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中任意一条边的斜率均不相同.将坐标系进行旋转,使得 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 上的一条边 𝑋𝑌XY![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 与 𝑥x![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 轴平行且在最下方.

设此时 𝑄Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中最低的点 𝑈U![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的 **最低** 且 **靠左** 的点 𝐴A![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

可知 ⃗𝐴 =⃗𝑋 +⃗𝑈A→=X→+U→![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),所以 𝐴A![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 必然在 𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的边界上.

同理,𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中 **最低** 且 **靠右** 的点 𝐵B![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 有 ⃗𝐵 =⃗𝑌 +⃗𝑈B→=Y→+U→![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),也必然在 𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的边界上.

因此,有 ⃗𝐴𝐵 =⃗𝑋𝑌 +⃗𝑈AB→=XY→+U→![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

若按顺序进行旋转,则结果连续的构成了 𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中的每条边.

证毕.

### 实现

我们可以根据性质 2,将凸集 𝑃,𝑄P,Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 极角排序,得到它们在 𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 上的出现顺序,把 𝑃1 +𝑄1P1+Q1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 看做 𝑃 +𝑄P+Q![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的起点,然后用类似 **归并** 的做法依次放边即可.

时间复杂度:𝑂(𝑛 +𝑚)O(n+m)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)

实现

---|---

例题

[例题 [JSOI2018] 战争](https://loj.ac/p/2549)

有两个凸包 𝑃,𝑄P,Q,平移 𝑞q 次 𝑄Q,问每次移动后是否有交点.1 ≤𝑛,𝑚 ≤105,1 ≤𝑞 ≤1051≤n,m≤105,1≤q≤105

实现

---|---

## 三维凸包

### 基础知识

> 圆的反演:反演中心为 𝑂O![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),反演半径为 𝑅R![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),若经过 𝑂O![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的直线经过 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),𝑃′P′![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),且 𝑂𝑃 ×𝑂𝑃′ =𝑅2OP×OP′=R2![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),则称 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)、𝑃′P′![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 关于 𝑂O![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 互为反演.

### 过程

求凸包的过程如下:

  * 首先对其微小扰动,避免出现四点共面的情况.
  * 对于一个已知凸包,新增一个点 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),将 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 视作一个点光源,向凸包做射线,可以知道,光线的可见面和不可见面一定是由若干条棱隔开的.
  * 将光的可见面删去,并新增由其分割棱与 𝑃P![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 构成的平面. 重复此过程即可,由 [Pick 定理](../pick/)、欧拉公式(在凸多面体中,其顶点 𝑉V![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)、边数 𝐸E![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 及面数 𝐹F![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 满足 𝑉 −𝐸 +𝐹 =2V−E+F=2![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7))和圆的反演,复杂度 𝑂(𝑛2)O(n2)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).1

### 模板题

[P4724【模板】三维凸包](https://www.luogu.com.cn/problem/P4724)

重复上述过程即可得到答案.

代码实现

---|---

练习

参考资料与注释

  1. 三维凸包学习小记
本页面最近更新: 2026/1/7 08:56:54,更新历史 发现错误?想一起完善?在 GitHub 上编辑此页! 本页面贡献者:Ir1d, H-J-Granger, StudyingFather, countercurrent-time, Enter-tainer, NachtgeistW, CCXXXI, ksyx, sshwy, Tiphereth-A, AngelKitty, c-forrest, cjsoft, diauweb, Early0v0, ezoixx130, GekkaSaori, Henry-ZHR, iamtwz, Konano, LovelyBuggies, lychees, Makkiy, mgt, minghu6, P-Y-Y, PotassiumWings, SamZhangQingChuan, Suyun514, weiyong1024, Xeonacid, xglight, AtomAlpaca, F1shAndCat, GavinZhengOI, Gesrua, gi-b716, gitbugfsj, kxccc, livrth, megakite, Menci, ouuan, Peanut-Tang, shawlleyw, shuzhouliu, Sshwy, SukkaW, wjy-yy 本页面的全部内容在CC BY-SA 4.0SATA 协议之条款下提供,附加条款亦可能应用