凸包算法
凸包是一个计算几何中的概念,其形式化定义为在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包
。在二维欧几里得空间中,凸包可想象为将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
常见的求解二维凸包的算法有很多种,这里只简单介绍一下Andrew算法。
以LeetCode587为例,这道题可以说是一道二维凸包的模板题。
Andrew算法正是用于求解凸包上的所有点(围成所有点的最小周长),其算法逻辑将凸包分为「上凸壳」和「下凸壳」,并分别求出,如下图所示,蓝色分割线将凸包分为两部分:

Andrew算法的流程如下:
- 1. 对所有点进行排序,先根据$x$坐标排升序,后根据$y$坐标排升序;根据$x$排升序的目的,是为了我们能够往一个方向画出凸包边缘(从左往右画出一半凸壳,从右往左画出另外一半),而将$y$升序目的是可以确保一旦我们现在从$a$到$b$进行连线,那么$a$到$b$之间的所有点能够确保被围住;
- 2. 使用栈来维护所有凸包上的点,或者说凸包上的边,会更为准确,因为凸包起点元素会在栈中出现两次(首尾),因此更为准确的描述应该是使用栈维护凸包的所有的边,栈中相邻元素即代表凸包上的一条边;
- 3. 分别「从左往右」和「从右往左」处理排序好的所有点,来分别画出凸包的上下两部分,根据画的是第一部分还是第二部分,维护栈内元的处理逻辑稍有不同:
- 第一部分(上凸壳):
- 3.1 若栈内元素少于2个,由于组成一条线至少需要两个点,说明此时第一条边都还没画出,直接将元素添加到栈中;
- 3.2 若栈内元素不少于2个,考虑是否要将栈顶的边删掉(由栈顶前两个元素组成的边)假设栈顶元素为$b$,栈顶元素的下一位为$a$,即栈顶存在一条$a$到$b$的边,当前处理到的点为$c$,此时我们根据$ac$边是否在$ab$边的顺时针方向来决定是否要将$ab$边去掉。
- 第一部分(上凸壳):

- 第二部分(下凸壳):
- 3.3 逻辑同理,唯一需要注意的是,第一部分的凸包边我们不能删去,假定处理完第一部分凸包,我们栈内有$m$个元素,我们需要将上述「栈顶元素不少于2个」的逻辑替换为「栈顶元素大于$m$个」,同时已参与到凸包第一部分的点,不能再考虑,因此需要额外使用一个$vis$数组来记录使用过的点。
为了方便取得栈顶的前两位元素,我们使用数组实现栈。
具体的AC代码如下:
1 | class Solution { |
end☆~