构造凸包-Jarvis March(Gift Wrapping)算法

回顾凸包构造算法:极点法、极边法和增量构造法,其复杂度分别为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部分)找到一个与当前局部解“紧密相关的元素”(相当于选取的最大元素)。这个思想为解决凸包问题带来了新思路。

对算法的大致过程进行描述(标识为:已找到极边数/所有极边数):

  • 在加入[partial convex hull]之前找出下一条极边

    首先从任何一个极点(后面说明如何找到这个点)开始(图中0/5),然后找到一条以这个极点为端点的极边(1/5)。接着沿着极边另一个端点(endpoint)出发,再找出下一条极边(2/5)。如此反复操作,最终会找到一条以最初极点为endpoint的极边,得到一个封闭的环,凸包也构造完成。凸包构造过程类似于选择排序中sorted不断向前扩展一样,不断扩展局部解,最后得到问题最终解。

凸包构造的问题由此分解为一个个子问题:如何从endpoint出发找到下一条极边
用to left test找到下一条极边

现分析如何从endpoint找到下一条极边。考虑一下的一般情况:

image-20200312175230158

我们从极点o开始寻找极边,假设当前找到的极边是ik,接下来要做的工作是找到从k出发的另一条极边ks,即找到极点s。

显然,s来自于其他那些尚未处理的点中,那么s与其他点相比有什么特征?观察发现,ik作为一条极边,它的右侧肯定都是空的,所有其他点都在ik左侧。画出k与其他候选点的有向直线,例如下图中的ks,kt:

image-20200312175450087

注意图中红色标出的角度,可以看出ks与ik的夹角比kt小,也就是ks比kt相较于ik偏左的角度更小。实际上ks偏左的角度比其他任何从k出发的边都小,这就是s点的判定依据

这样就找到了从其余点中选择s点的思路:任选两个点,从k出发过这两点做有向边,看哪个偏左的角度更小就留下,另一点丢弃。然后再拿一点与留下的点比较,反复这个过程,最终留下的就是要找的s点。

问题至此转化为:如何比较两条有向边(例如ks和kt)相较于另一有向直线(例如ik)谁偏左的角度更小。

当然可以通过计算三角函数的方法来比较,这是最直观的数学思维。但是这样计算十分复杂,更重要的是引入了误差。这时候又要使用to left test这个基础方法来解决问题了

image-20200312175904984

具体做法就是以在ks和kt中以任意个为基准(如以ks为基准),对另一点(如t)做to left test。上图点t和有向边ks的to left test结果为true,t在ks左边,因此ks偏左的角度更小,舍弃点t。

类比选择排序来理解:

选择排序中的选择过程需要比较元素大小,就要由一种比较器完成,而上述比较偏角的过程也可以抽象为一种比较器的操作。构造凸包的算法框架与选择排序相同,只是比较器替换为to left test而已

此处只是考虑一般情况,一些特殊细节未进行处理。例如在st上有s和s’两点,这两点的取舍问题未考虑。当然为了理解算法整体框架忽略特殊情况是很必要的。

确定第一个极点

一个细节:上文一开始提到的算法的最开始的第一个极点如何确定?

任何一个极点都可以使用,我们没必要去计算出哪个点是极点。可以选取y坐标最小的点,也就是最低点,在没有退化的情况下,这个点一定是一个极点。如果情况退化,有多个最低点(如例图中所示),我们就去选x坐标最小的那个点,也就是最左边的点即可。这种方法选出的点称为lowest-then-leftmost point(LTL)注意选取的规则的先后顺序,先选lowest,若点不唯一再选leftmost。

Jarvis March

类比选择排序的过程,我们得到的凸包构造算法就是Jarvis March算法,又称gift wrapping算法(算法过程如包装礼物一样)。接下来看算法具体实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
bool ToLeft(Point P, Point q, Point s)
{
    int area2 = p.x*q.y - p.y*q.x + q.x*s.y -q.y*s.x + s.x*p.y - s.y*p.x;
    
    return area2 >0 ; //左侧为真
}


int LTL (Point S[], int n)
{
    int ltl = 0; //the lowest-then-leftmost point
    for(int k=1; k<n;k++){
        if(S[k].y < S[ltl].y || (S[k].y==S[ltl].y && S[k].x<S[ltl].x))
            ltl =k;
    }
	return ltl;
}


void Javis(Point S[], int n)
{
    for (int k=0; k<n; k++)
    	S[k].extreme =FALSE; //首先将所有点标记为非极点
    
    int ltl=LTL(S, n); //找到LTL
    int k=ltl;  //将LTL作为第一个极点
    
    do
    {
    	S[k].extrem = true; int s =-1; //s是要找的下一个极点,用t去循环找
        
        for (int t=0; t<n; t++)
        {
            if(t != k &&t != s && ( s == -1 || !ToLeft(S[K],S[s],S[t]) ) )
                s = t; //如果t在pq的右侧,则更新s
        }
        S[k].succ = s; k = s; //新的极边pq确定   
        k = s; //更新k的值,变为下一次查找的起点
    }while(ltl != k) //如果循环回到了原来的点,则结束
}

梳理

  1. 首先,初始化将所有点都设为:非极点
  2. 然后,开始找到第一个极点(用LTL方法),找到之后赋值给k
  3. 接下来,开始找下一个极点s:
    • 主要用ToLeft Test来找出下一个极点
  4. 循环第3步骤,直到找到所有极点

小结

最后分析Jarvis March算法相较于增量构造法的优势。二者都是O(n^2^)的复杂度,Jarvis March算法的优势在于其的“输出敏感性(output sensitive)”。考虑点集S,共有n个点,来构造S上的凸包。

何为“输出敏感性”?Jarvis March算法每次新加入一条边都会耗费n的复杂度,但是构造过程一共会加入的边数往往比n少。如下图(设n = 7):

img

在非退化为共线的前提下,最好情况为只加入3条边(复杂度为O(3n)),最坏情况为所有点都是极点,加入n-1条边(复杂度为O(n^2^))。实际情况中最坏情况出现的几率很小,我们引入一个指标h来衡量凸包的极边数(the size of convex hull):
$$
h = |CH(S)|
$$
Jarvis March算法算法的复杂度更准确的表示为O(nh)。h又由最终输出结果,即凸包本身来决定,输出结果决定了构造过程的复杂度,这就是所谓的“输出敏感性”。这种类型的算法又被称为output sensitive algorithm。这种特性在其它凸包算法中也会体现。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值