[转] 凸包求解算法

来源:互联网 发布:更改防火墙端口 编辑:程序博客网 时间:2024/05/22 10:46

转自http://blog.csdn.net/bone_ace/article/details/46239187

0 引

       所谓凸包即选定点的连线组成的多边形能够将其它所有的点都包进来,这些点的连线组成的多边形即称之为凸包,如下图所示。凸包的作用可以是求解目标区域的轮廓,图像分割等图像高级形态学的操作。


1 数学基础

如何判断一个点 p3 是在直线 p1p2 的左边还是右边呢?(坐标:p1(x1,y1),p2(x2,y2),p3(x3,y3))


当上式结果为正时,p3在直线 p1p2 的左侧;当结果为负时,p3在直线 p1p2 的右边。

2 Graham 算法

时间复杂度:O(n㏒n) 

思路:Graham扫描的思想和Jarris步进法类似,也是先找到凸包上的一个点,然后从那个点开始按逆时针方向逐个找凸包上的点,但它不是利用夹角。 

步骤

1 把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点,如图中的P0。

[ 当纵坐标相同时,则取其中横坐标最小的点]

[ 这是基于极坐标系算法的选取规则,另外一种常见的方法是选择最左边的,对应的是笛卡尔坐标系 ]

2 把所有点的坐标平移一下,使 P0 作为原点,如上图。

3 计算各个点相对于 P0 的幅角 α ,按从小到大的顺序对各个点排序。当 α 相同时,距离 P0 比较近的排在前面。例如上图得到的结果为 P1,P2,P3,P4,P5,P6,P7,P8。我们由几何知识可以知道,结果中第一个点 P1 和最后一个点 P8 一定是凸包上的点。

(以上是准备步骤,以下开始求凸包)

以上,我们已经知道了凸包上的第一个点 P0 和第二个点 P1,我们把它们放在栈里面。现在从步骤3求得的那个结果里,把 P1 后面的那个点拿出来做当前点,即 P2 。接下来开始找第三个点:

4 连接P0和栈顶的那个点,得到直线 L 。看当前点是在直线 L 的右边还是左边。如果在直线的右边就执行步骤5;如果在直线上,或者在直线的左边就执行步骤6。

5 如果在右边,则栈顶的那个元素不是凸包上的点,把栈顶元素出栈。执行步骤4。

6 当前点是凸包上的点,把它压入栈,执行步骤7。

检查当前的点 P2 是不是步骤3那个结果的最后一个元素。是最后一个元素的话就结束。如果不是的话就把 P2 后面那个点做当前点,返回步骤4。

最后,栈中的元素就是凸包上的点了。 

[ 上述算法,并不是求得最小凸包的算法,因为步骤4中,始终是以P0与栈顶元素的连线去判断以上图为例,去判断,显然最后得到的是P0-P3-P4-P6-P7-P8的凸包,而事实上,应该是没有P4的。所以修正后的,应该是以栈中倒数第2个元素到栈顶元素的向量为基准来判断 当前点是在左侧还是右侧 ]

[ 步骤4时,是以左右比较通俗的语言描述,事实上从向量角度出发,即是判断次顶元素、栈顶元素,当前点,组成的两个向量的夹角,而上述求左右两边的行列式其实是等价于求两个向量的叉乘结果]

3 其它算法

解一:穷举法(蛮力法)

时间复杂度:O(n³)。

思路:两点确定一条直线,如果剩余的其它点都在这条直线的同一侧,则这两个点是凸包上的点,否则就不是。

步骤

将点集里面的所有点两两配对,组成 n(n-1)/2 条直线。

对于每条直线,再检查剩余的 (n-2) 个点是否在直线的同一侧。

如何判断一个点 p3 是在直线 p1p2 的左边还是右边呢?(坐标:p1(x1,y1),p2(x2,y2),p3(x3,y3))

 

当上式结果为正时,p3在直线 p1p2 的左侧;当结果为负时,p3在直线 p1p2 的右边。

解二:分治法

时间复杂度:O(n㏒n)。

思路:应用分治法思想,把一个大问题分成几个结构相同的子问题,把子问题再分成几个更小的子问题……。然后我们就能用递归的方法,分别求这些子问题的解。最后把每个子问题的解“组装”成原来大问题的解。 

步骤

把所有的点都放在二维坐标系里面。那么横坐标最小和最大的两个点 P1 和 Pn 一定是凸包上的点(为什么呢?用反证法很容易证明,这里不详讲)。直线 P1Pn 把点集分成了两部分,即 X 轴上面和下面两部分,分别叫做上包和下包。

对上包:求距离直线 P1Pn 最远的点,即下图中的点 Pmax 。

作直线 P1Pmax 、PnPmax,把直线 P1Pmax 左侧的点当成是上包,把直线PnPmax 右侧的点也当成是上包。

重复步骤 2、3。

对下包也作类似操作。

然而怎么求距离某直线最远的点呢?我们还是用到解一中的公式:

设有一个点 P3 和直线 P1P2 。(坐标:p1(x1,y1),p2(x2,y2),p3(x3,y3)) 

对上式的结果取绝对值,绝对值越大,则距离直线越远。

注意:在步骤一,如果横坐标最小的点不止一个,那么这几个点都是凸包上的点,此时上包和下包的划分就有点不同了,需要注意。

解三:Jarvis步进法

时间复杂度:O(nH)。(其中 n 是点的总个数,H 是凸包上的点的个数) 
思路

纵坐标最小的那个点一定是凸包上的点,例如图上的 P0。

从 P0 开始,按逆时针的方向,逐个找凸包上的点,每前进一步找到一个点,所以叫作步进法。

怎么找下一个点呢?利用夹角。假设现在已经找到 {P0,P1,P2} 了,要找下一个点:剩下的点分别和 P2 组成向量,设这个向量与向量P1P2的夹角为 β 。当 β 最小时就是所要求的下一个点了,此处为 P3 。

注意:

找第二个点 P1 时,因为已经找到的只有 P0 一个点,所以向量只能和水平线作夹角 α,当 α 最小时求得第二个点。

共线情况:如果直线 P2P3 上还有一个点 P4,即三个点共线,此时由向量P2P3 和向量P2P4 产生的两个 β 是相同的。我们应该把 P3、P4 都当做凸包上的点,并且把距离 P2 最远的那个点(即图中的P4)作为最后搜索到的点,继续找它的下一个连接点。

解五:Melkman算法

说真的,这个算法我也还没有看清。网上的资料也少的可怜,我暂且把网上的解释截个图在这里,往后搞懂以后再回来补上。 或者有人看懂了的,希望不吝指教,不甚感激!