回顾凸包构造算法:极点法、极边法和增量构造法,其复杂度分别为O(n^4^)、O(n^3^)和O(n^2^),效率经过优化已经大大提高了。接下来引入一种新的算法——Jarvis March,其复杂度也是O(n^2^),但是相较于增量构造在最好情况下效率是较高的。
实现策略
先想一下为何极边法复杂度高达O(n^3^)。实际上我们要对点集中所有边进行遍历,这需要n^2^复杂度,然后对每个边进行鉴别,又需要n复杂度,因此总体复杂度高达O(n^3^)。那么该如何改进呢?这就可以运用选择排序的思想:将下一个要查找的边缩小到一个小范围,而非遍历所有边。
选择排序每次选取出的unsorted最大元素放在sorted 部分的首部,也就意味着整个unsorted部分必然不会超过sorted部分。从算法整体框架考虑,每次我们都是维护一个局部解(也就是sorted部分),然后从尚未处理的部分(也就是unsorted部分)找到一个与当前局部解“紧密相关的元素”(相当于选取的最大元素)。这个思想为解决凸包问题带来了新思路。
对算法的大致过程进行描述(标识为:已找到极边数/所有极边数):
首先从任何一个极点(后面说明如何找到这个点)开始(图中0/5),然后找到一条以这个极点为端点的极边(1/5)。接着沿着极边另一个端点(endpoint)出发,再找出下一条极边(2/5)。如此反复操作,最终会找到一条以最初极点为endpoint的极边,得到一个封闭的环,凸包也构造完成。凸包构造过程类似于选择排序中sorted不断向前扩展一样,不断扩展局部解,最后得到问题最终解。
凸包构造的问题由此分解为一个个子问题:如何从endpoint出发找到下一条极边。 |
---|
用to left test找到下一条极边
现分析如何从endpoint找到下一条极边。考虑一下的一般情况:
我们从极点o开始寻找极边,假设当前找到的极边是ik,接下来要做的工作是找到从k出发的另一条极边ks,即找到极点s。
显然,s来自于其他那些尚未处理的点中,那么s与其他点相比有什么特征?观察发现,ik作为一条极边,它的右侧肯定都是空的,所有其他点都在ik左侧。画出k与其他候选点的有向直线,例如下图中的ks,kt:
注意图中红色标出的角度,可以看出ks与ik的夹角比kt小,也就是ks比kt相较于ik偏左的角度更小。实际上ks偏左的角度比其他任何从k出发的边都小,这就是s点的判定依据。
这样就找到了从其余点中选择s点的思路:任选两个点,从k出发过这两点做有向边,看哪个偏左的角度更小就留下,另一点丢弃。然后再拿一点与留下的点比较,反复这个过程,最终留下的就是要找的s点。
问题至此转化为:如何比较两条有向边(例如ks和kt)相较于另一有向直线(例如ik)谁偏左的角度更小。 |
---|
当然可以通过计算三角函数的方法来比较,这是最直观的数学思维。但是这样计算十分复杂,更重要的是引入了误差。这时候又要使用to left test这个基础方法来解决问题了
选择排序中的选择过程需要比较元素大小,就要由一种比较器完成,而上述比较偏角的过程也可以抽象为一种比较器的操作。构造凸包的算法框架与选择排序相同,只是比较器替换为to left test而已。
此处只是考虑一般情况,一些特殊细节未进行处理。例如在st上有s和s’两点,这两点的取舍问题未考虑。当然为了理解算法整体框架忽略特殊情况是很必要的。
确定第一个极点
一个细节:上文一开始提到的算法的最开始的第一个极点如何确定?
Jarvis March
类比选择排序的过程,我们得到的凸包构造算法就是Jarvis March算法,又称gift wrapping算法(算法过程如包装礼物一样)。接下来看算法具体实现方法。
|
|
梳理
小结
何为“输出敏感性”?Jarvis March算法每次新加入一条边都会耗费n的复杂度,但是构造过程一共会加入的边数往往比n少。如下图(设n = 7):