计算几何——凸包问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计算几何——凸包问题相关的知识,希望对你有一定的参考价值。

注:本文是2016年春季清华大学邓俊辉老师《计算几何》MOOC课程的简要个人总结系列之一,我将同步课程内容更新。不过有可能写的不完全是课程内容,也包含一些个人理解。如果你在看完本文后开始对计算几何感兴趣,请前往相应的MOOC平台完整学习邓老师的课程。如此精心设计和编排的课程,不应该被辜负。在此感谢邓老师!

Knowledge Dependence:阅读本文前你只需要有基本的几何知识和算法知识即可。代码实现需要一丢丢C++基础。

由于作者很懒不想画图,所以你还需要一点脑内小剧场。不过相信我,自己在脑中一步一步地形象化问题将会很有意思。不信?往下看咯。

极边(Extreme Edge)算法

在上一篇中,我们把注意力放在了极点上,并得出了一个时间复杂度高达O(n^4)的算法,这是不能接受的。

现在,我们转换我们的注意力,来关注极边。

什么是极边?也很简单:组成凸包的边就是极边。

那我们如何利用极边来求解凸包呢?跟极点算法一样,我们同样来考查极边的特点。

对于任意两极点所组成的线段(其中包括我们要找的「极边」),我们可以明显地发现,极边右侧一定是空的(同上一篇,规定逆时针方向为正方向)!也就是是说,对于给定的点集,极边右侧是没有点的!而对于不是极边的那些有向线段,我们也可清晰地发现它的两侧都肯定不是空的。

基于极边的这个特点,我们可以自然地得到这样的找极边算法:

首先枚举给定点集的任意两点组成的「有向线段」(Directed Edge),然后对于当前的这条有向线段,我们再枚举所有点(除组成有向线段顶点的两点),并一一判断该点是否在这条有向线段的右侧,如果在,那就说明我们当前枚举到的这条有向线段并不是我们要找的极边。

同样值得一提的是,在具体实现的过程中,由于我们是枚举任意两点来确定枚举的有向线段,但「有向」这件事并不是那么好确定的。不过,好在实际上,我们并不需要严格地判别点是不是在该有向线段的「右」侧,而只需要判别出该有向线段的「某一」侧是否为空的即可。

那如何判断点在有向线段的哪一侧呢?

好熟悉的问题啊!没错,上一篇中我们同样地曾经把问题化归成了这个子问题!答案就是利用2倍有向面积来做 ToLeft Test!

至此,算法已经清晰明了。

具体的代码实现如下,代码同样十分清晰,我依然建议你跟着代码再清楚地梳理一遍:

 1 /*******************************
 2  * Extreme_Edge_Algorithm for Convex Hull construction
 3  * Time Complexity:    O(n^3)
 4 ********************************/
 5 
 6 void markEE(Point S[], int n) {
 7     // 初始化:“有罪推论”,即预设所有点都不是极点(也就是所有有向线段都不是极边)
 8     for (int k = 0; k < n; k++) S[k].extreme = false;
 9     // 枚举所有的“有向边”
10     for (int p = 0; p < n; p++)
11         for (int q = p + 1; q < n; q++)
12             checkEdge(S, n, p, q);    // directed edge pq
13 }
14 
15 void checkEdge(Point S[], int n, int p, int q) {
16     // 初始化:先假设有向边 pq 两边都都空的(即没有点)
17     bool LEmpty = true, REmpty = true;
18     // 枚举所有除 p, q 外的点,并对其与有向边 pq 进行 ToLeft Test
19     for (int k = 0; k < n && (LEmpty || REmpty); k++) {
20         if (k != p && k != q) {
21             // 如果 ToLeft Test 为真则左边不为空,否则右边不为空
22             ToLeft(S[p], S[q], S[k]) ? LEmpty = false : REmpty = false;
23         }
24     }
25     // 如果有向边 pq 有一侧为空,则 pq 即为极边,组成极边的顶点即可标记为
26     if (LEmpty || REmpty)
27         S[p].extreme = S[q].extreme = true;
28 }
29 
30 bool ToLeft(Point p, Point q, Point s) {
31     // 将 ToLeft Test 等价为求2倍“方向面积”,避免除法或三角运算带来的精度问题
32     return Area2(p, q, s) > 0;
33 }
34 
35 int Area2(Point p, Point q, Point s) {
36     // 2倍“有向面积”
37     return
38         p.x * q.y - p.y * q.x
39         + q.x * s.y - q.y * s.x
40         + s.x * p.y - s.y * p.x;
41 }

同样地,我们来分析算法的复杂度。

首先我们枚举有向线段是一个二重循环,然后对于每一个有向线段需要枚举一遍所有的点来进行 ToLeft Test,而 ToLeft Test 是常数时间的。

是的,极边算法是的时间复杂度是 O(n^3) 的。

尽管相对于上一篇的极点算法,我们已经把时间复杂度降低了一个数量级,然后这样的复杂度我们依然是难以接受的,我们依然需要一个新的角度来寻找更优的算法。

 

【To Be Continued】

以上是关于计算几何——凸包问题的主要内容,如果未能解决你的问题,请参考以下文章

计算几何——凸包问题

Cows 计算几何 求凸包 求多边形面积

[HDU4316]Mission Impossible(计算几何/凸包/半平面交)

P2742 [USACO5.1]圈奶牛Fencing the Cows /模板二维凸包(计算几何)(凸包)

P2742 [USACO5.1]圈奶牛Fencing the Cows /模板二维凸包(计算几何)(凸包)

P2742 [USACO5.1]圈奶牛Fencing the Cows /模板二维凸包(计算几何)(凸包)