计算几何——凸包问题

Posted

tags:

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

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

 

Knowledge Dependence:

阅读本文前你只需要有基本的几何知识和算法知识即可。代码实现需要一丢丢C++基础。由于作者很懒不想画图,所以你还需要一点脑内小剧场。不过相信我,自己在脑中一步一步地形象化问题将会很有意思。不信?往下看咯。

写在前面

计算几何就是几何版本的「算法与数据结构」,此话不假。她即有算法的抽象性和逻辑性,又有几何的形象与直观。这种兼具两种看似矛盾特性的特点,使得她成为了一门十分有趣味性的学科。

「计算几何」,重在「计算」。即我们如借助计算机,选取高效的算法,来解决几何问题。看,最终依然落脚在了「算法」上,所以要是说「计算几何」是包上了「几何」外衣的「算法设计」,完全可以。

既然研究的是算法,那一定也是很多更高层学科的基础。是的,诸如图形学、CAD,甚至是人工智能的路径规划等问题,都可以看到计算几何的身影。

每个学科都有其重要且基础的基本问题,计算几何也不例外,凸包问题便是在计算几何占据这样地位的问题。

接下来我将介绍凸包问题。邓老师曾说过,他希望他的课高中毕业生也能听懂,我也将尽我所能以精简明晰的语言来介绍。

 

什么是凸包(Convex Hull)

严格的数学定义是乏味难懂的,简单的说,在一面墙上的一块区域钉了很多钉子,你用一条橡皮筋,把它扯大使其能够包括所有的钉子,然后松手,此时橡皮筋形成的多边形就是凸包。

可以简单地看出,凸包至少有这样两个特点:

  • 封闭的简单多边形(中间不含孔洞);
  • 所有的内角都小于180°;

所谓的凸包问题,就是在给定这些「钉子」(点集)的情况下,如何快速求出「橡皮筋」(凸包)的问题。

 

极点(Extreme Point)算法

什么是极点?

很简单,参与了固定「橡皮筋」的「钉子」就是极点。

现在我们把注意力放到极点上。为什么呢?因为如果我们求出了所有的极点自然就已经求出凸包了吖。

那如何确定给定点集中哪些点会是极点呢?或者换一个问法是,极点和其他点有什么本质上的不同呢?

很清楚地我们可以发现,极点不会被包含在由任何其他三个点构成的三角形中,而非极点一定可以找到由其他三个点组成的三角形,将它包围起来。

所以,很自然地,我们可以得到这样的找极点算法:

枚举可以由给定点集组成的所有的三角形,对于每一个这样的三角形,再枚举所有点(不包含组成这样三角形的顶点),如果对于当前点,它落在这个三角形内,那么我们可以立即判定它一定不是极点,可以排除在外。最后在所有的枚举完成之后,所有未被我们排除的点即构成极点。我们此时也就得到了凸包。

看似很清楚简明,完了没?

……还有一个问题,我们如何编码实现判断一个点是不是落在某个三角形之类呢?

稍加思考我们就会发现,我们需要把问题进一步转化成跟简单的问题。也就是,判断出点与三角形之间的关系。

我们定义逆时针为正方向,把三角形的三条边都看成有向线段。

此时,如果我们可以判断出点都在三角形三条边的左侧,则该点一定是落在三角形内的。可以一一枚举验证,反之亦反。

是的,问题被更加简化了。

再来,我们如何判断一个点在有向线段的左侧?

学过解析几何的朋友一定对这个问题不陌生,「有向面积」(Directed Area)这个概念就可以拯救我们(如果你还不清楚,see:有向面积_百度百科)。在我们当前定义的正方向的前提下,如果点和有向线段的两个断点之间构成的有向面积是正的,那么这个点就一定在这个有向线段的左侧。注意,这个方法在我们后面的算法中依然会经常用到,所以如果你对此还有疑惑,请一定去了解清楚。

OK,至此,我们的问题已经全部解决了。

以上所有分析问题的过程,无处不体现了数学中的「化归」思想,或者说算法中「分治」思想,即把一个大问题分解成更易解决的小问题,层层化归,最后变成我们可以解决的问题,原问题自然就解决了。这,也就是我们中国人常说的「大事化小,小事化了」。你会发现,后面我们依然会经常运用这种思想。

代码实现:(每一步都非常清晰,且有注释,建议结合代码再梳理一遍思路)

 1 /*******************************
 2  * Extreme_Point_Algorithm for Convex Hull construction
 3  * Time Complexity:    O(n^4)
 4 ********************************/
 5 
 6 void extremePoint(Point s[], int n) {
 7     // 初始化:“无罪推论”,认为所有的点都是极点,再一一排除
 8     for (int s = 0; s < n; s++) S[s].extreme = true;
 9 
10     //枚举所有的三角形
11     for (int p = 0; p < n; p++)
12         for (int q = p + 1; q < n; q++)
13             for (int r = q + 1; r < n; r++) {
14                 // 枚举所有点
15                 for (int s = 0; s < n; s++) {
16                     // 去掉顶点和已经被判定为非极点的点
17                     if (s == p || s == q || s == r || !S[s].extreme)
18                         continue;
19                     // 进行 InTriangle Test
20                     if (InTriangle(S[p], S[q], S[r], S[s]))
21                         S[s].extreme = false;
22                 }
23             }
24 }
25 
26 bool InTriangle(Point p, Point q, Point r, Point s) {
27     // 分别对三角形每条边做 ToLeft Test
28     bool pqLeft = ToLeft(p, q, s);
29     bool qrLeft = ToLeft(q, r, s);
30     bool rpLeft = ToLeft(r, p, s);
31     // 如果 ToLeft Test 结果都一样则说明在此三角形中
32     return (pqLeft == qrLeft) && (qrLeft == rpLeft);
33 }
34 
35 bool ToLeft(Point p, Point q, Point s) {
36     // 将 ToLeft Test 等价为求2倍“方向面积”,避免除法或三角运算带来的精度问题
37     return Area2(p, q, s) > 0;
38 }
39 
40 int Area2(Point p, Point q, Point s) {
41     // 2倍“有向面积”
42     return
43         p.x * q.y - p.y * q.x
44         + q.x * s.y - q.y * s.x
45         + s.x * p.y - s.y * p.x;
46 }

虽然我们看似已经把这个问题给解决了,但是熟悉算法复杂度分析的朋友可能一开始就意识到了,这可是个时间复杂度高达 O(n^4) 的算法,在实际应用中这完全是无法被接受的!

所以,在本系列文章的接下来的几篇中,我们将一步一步地,介绍复杂度越来越低的更多计算几何经典算法。同时我们也会简要分析一下凸包问题的理论时间复杂度下界,并且你会看到,的确存在可以达到理论时间复杂度下界的算法。

 

【To Be Continued】

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

计算几何——凸包问题

计算几何——凸包问题

计算几何中的凸包类问题

初学计算几何——初识凸包

计算几何--凸包总结

计算几何:凸包