凸包概念
在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点的凸组合来构造。在二维欧几里得空间中,凸包可以想象为一条包着所有点的橡皮绳。
- 对于一个集合D,D中任意有限个点的凸组合的全体称为D的凸包。
- 对于一个集合D,所有包含D的凸集的交集称为D的凸包。
这两种定义是等价的。
解法
暴力解法
每次枚举两个点,这两个点确定一条直线,如果剩余的其他点都同在这条线的某一侧,则这两个点是凸包上的点,否则就不是。时间复杂度为O(n^3)。具体实现方面,点集中的点两两配对,组成n(n-1)/2条直线;对于每条直线,再检查剩余的n-2个点是否在直线的同一侧。
判断方向
左右方向是相对前进方向的,只要指定了前进方向就知道左右。因此,点在直线的左右侧可以用矢量来判断。
平面上三个点A(x1, y1),B(x2, y2),C(x3, y3)的面积为:S(A, B, C) = (x1-x3)(y2-y3) + (x2-x3)(y1-y3)。当ABC三点逆时针时S为正,当ABC三点顺时针时S为负。假定A为矢量起点,B为矢量终点,C为要判断的点,则有:
- S(A, B, C)为正数,C在矢量AB的左侧;
- S(A, B, C)为负数,C在矢量AB的右侧;
- S(A, B, C)为0,C在直线AB上。
分治
对暴力解法进行优化。可以把大问题分解成结构相同的小问题,再用递归的方法求小问题的解,再还原为大问题的解。对于凸包问题而言,可以把所有的点都放在二维坐标系内,则横坐标最小和最大的两个点P1和Pn一定是凸包上的点。直线P1Pn把点集分为了上下两部分。上面的部分求距离直线P1Pn最远的点,为Pmax。做直线P1Pmax、PnPmax,把P1Pmax左侧的点当成新的上半部分,PnPmax右侧的点当成新的上半部分。不断重复上述步骤,就可以找到凸包点集。时间复杂度为O(nlogn)
点到直线距离
方法和暴力解法判断点在线的左右侧的方法一样,只是需要对结果再求个绝对值。绝对值最大的就是离直线最远的点。
Jarvis步进法
纵坐标最小的点一定是凸包上的点,假定这个点为P1,从这个点开始,按逆时针方向,逐个找凸包上的点,每前进一步找到一个点。找下一个点的方法是利用夹角。假定已经找到三个点P1P2P3,要找到下一个点,剩下的点分别和P2组成向量,假定这个向量与向量P1P2的夹角为β,当β最小时,这个点就是要找的下一个点,假定为P4。重复上述步骤不断寻找下一个点,直到找到的下一个点为P1结束算法。需要特别注意的是找第二个点P2时,因为已经找到P1,所以向量只能和水平线作夹角α,当α最小时确定第二个点P2。当出现共线情况时,假定直线P2P3上还有一个点P4,即三点共线,此时向量P2P3和向量P2P4产生的β角是相同的,因此应该把P3P4都当做凸包上的点,并且把离P2最远的点作为最后搜索的点,继续寻找下一个点。时间复杂度为O(nH),H为凸包上的点的个数。
Graham扫描法
选取最下的点P1;计算出每个点相对P1的角度和距离排序;设点数为n,将P[n-1]和P[0]入栈,判断点集合是否为一条直线;从1到n-1遍历,对于P[k-1],P[k-2],P[i]若满足左转,将P[i]入栈,否则I–,k–,k初始值为2。k–,返回k表示凸包的点数。
Andrew算法
点排序,删除重复点,得到点集P1,P2…。首先将P1P2放入凸包。从P3开始,当新节点在前进方向的左边时继续,否则依次删除最近加入到凸包中的点,直到新点在左边。