凸包算法总结

一、定义:

凸包是一个相对于点集的概念,对于一个已经确定的点集,凸包就是由其中某些点构成的一个子集,这个子集中的点构成一个凸多边形,该多边形完全包围点集中所有点。

关于凸包有一个形象的比喻:把点集中各点看作钉子,拿一个橡皮筋套住所有的钉子,最终橡皮筋就是一个凸包,使橡皮筋绷紧的钉子就是凸包中的顶点。

凸包算法总结_第1张图片

二、求法:

目前比较常见的两种求法分别为Graham扫描法和Andrew算法,由于两个算法都需要对点进行排序,因此它们的时间复杂度均为O(nlogn)。接下来会详细介绍Graham扫描法,因为Andrew算法类似Graham扫描法,所以只简略介绍。

Graham扫描法

这个算法需要对点集内所有点进行极角排序,极角排序时的基准点可以选最左下的点,也可以选最右上的点,其实只要选一个一定出现在凸包上的点即可。做极角排序主要有两个作用,一个是排序后按照点的顺序可以构成一个多边形,不会出现边和边的交叉,也就是下面这种情况:

凸包算法总结_第2张图片

另一个作用是点的顺序是多边形顶点逆时针的顺序,当然如果是顺时针也是可以的,不过后面代码也要相应的变化罢了,如下图: 

凸包算法总结_第3张图片

正是由于极角排序之后有这两条性质才能进行下面的算法,另外按极角排序只是能够比较方便地满足这两条性质,其实不一定非要按照这个顺序来,只要按照点的顺序能逆时针构成多边形就可以进行下面的操作。

接下来是Graham扫描法最重要的部分,首先极角排序后得到的多边形如下:

凸包算法总结_第4张图片

然后设置一个栈用来保存最终凸包上的顶点,p0显然是最终凸包上的顶点,所以把p0直接入栈,之后按照点的顺序遍历之后的每一个点,对于每个点先假设它是凸包上的顶点将其入栈,比如现在需要入栈p1:

凸包算法总结_第5张图片

当栈中至少有两个元素时入栈前需要判断一下是否需要把某些元素弹出,如果当前点在栈顶两个元素构成向量的右侧则需要弹出栈顶元素,否则不需要。判断一个点在向量哪侧则是通过叉乘判断的。对于p2而言,它在向量p0->p1的左侧,因此直接入栈:

凸包算法总结_第6张图片

同理,p3在向量p1->p2的左侧,直接入栈:

凸包算法总结_第7张图片

接下来由于p4在向量p2->p3的右侧,此时需要先弹出一些元素,直到p4在栈顶两元素的左侧为止。也就是需要把p3弹出,然后判断一下发现p4在向量p1->p2的左侧了,停止出栈并将p4入栈。为什么要这样处理呢?原理是栈中保存的点都只是暂时可能出现在凸包上的点,在p4没有出现前p0->p1->p2->p3构成的确实是某个凸多边形的一部分,但是p4出现在了这个凸多边形的外侧,这是不满足凸包定义的:凸包要包围所有的顶点,因此说明p3不能作为凸包顶点,否则会出现矛盾。把p3出栈以后它之前的点也是有可能导致p4出现在凸包外侧的,因此要一直出栈直到p4出现在正在构造的凸多边形的内部,而只要p4在栈顶两元素左侧就算出现在凸多边形内部了,这里就用到了刚才的第一个性质了,由第一个性质可知p4在栈顶两元素左侧可以推出p4在之前构造的所有边左侧,也就不用每次都检查到栈底了,这点使得这部分的时间复杂度降为O(n)。将p4入栈后如下图:

凸包算法总结_第8张图片

之后将p5入栈且p5入栈前并不需要出栈:

凸包算法总结_第9张图片

遍历完所有点后栈中就保存了最终凸包上的顶点,红线即为最终凸包:

凸包算法总结_第10张图片

极角排序复杂度O(nlogn),遍历点集复杂度O(n),综合起来是O(nlogn)的复杂度。

Andrew算法

如果彻底搞懂了Graham扫描法那理解Andrew算法也就很简单了。其实二者不同处也就是一开始的排序方式不同,Andrew算法是把点集按照x升序排序,x若相同则按y升序排序,这样排完序后会形成一条方向大体向右的折线,以Graham扫描法中的点集为例,如下图所示:

凸包算法总结_第11张图片

之后的操作就和Graham扫描法基本一致了,也是按点的顺序将所有点入栈,同时过程中弹出一些不满足的点,最后栈中就保留了一个下凸包。为什么只有一半?因为x坐标是不减的,显然只能获得凸包的一部分点。之后再倒序作相同处理就可以得到上凸包,二者合起来就是一个完整的凸包。对点排序复杂度O(nlogn),遍历点集复杂度O(n),综合起来也是O(nlogn)的复杂度。

三、总结

这两种求凸包的算法时间复杂度都差不多,因此也不分什么优劣,具体用哪种算法看个人喜好了,我比较喜欢用Graham扫描法,感觉比较清晰明了......

你可能感兴趣的:(算法学习,计算几何,算法)