计算几何——凸包问题(一)

注:本文是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】

时间: 2024-08-24 09:42:21

计算几何——凸包问题(一)的相关文章

计算几何--凸包总结

计算几何凸包 凸包:给你n个散落的点,让你求出最小的凸多边形将所有的点包括起来,或者点在边上.用到的算法是Graham Graham算法:首先找到一个顶点作为基点,然后将这个点与其他点进行连线,然后按照角度的大小进行排序. 然后加点,第一个点肯定在凸多边形上,然后开始加点,每加一个点,要用向量的×积判断以前是不是有边在他的里面,如果有的话,就将这个点替换,这个过程是一个回溯的过程 首先找基点,纵坐标最小的一定在凸包上,如果纵坐标相等那么就选横坐标小的. 下面模拟一下Graham算法扫描的过程(截

计算几何 --- 凸包 模板

//Memory Time // 1347K 0MS // by : Snarl_jsb #include<algorithm> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<vector> #include<queue> #include<stack> #include<map> #

[笔记] 计算几何-凸包 POJ-3348 Cows

题目:http://poj.org/problem?id=3348 求凸包面积 算法:先对点的横坐标排序,从左到右先计算下凸边,再从右到左计算上凸边.复杂度比Graham Scan法稍稍要高(两次遍历点集),但实现较容易 #include <stdio.h> #include <algorithm> using namespace std; struct point{ double x,y; }; point a[10005]; point res[10005]; bool cmp

uva 10652 Board Wrapping (计算几何-凸包)

Problem B Board Wrapping Input: standard input Output: standard output Time Limit: 2 seconds The small sawmill in Mission, British Columbia, has developed a brand new way of packaging boards for drying. By fixating the boards in special moulds, the b

计算几何——凸包问题(三)

注:本文是2016年春季清华大学邓俊辉老师<计算几何>MOOC课程的简要个人总结系列之一,我将同步课程内容更新.不过有可能写的不完全是课程内容,也包含一些个人理解.如果你在看完本文后开始对计算几何感兴趣,请前往相应的MOOC平台完整学习邓老师的课程.如此精心设计和编排的课程,不应该被辜负.在此感谢邓老师! Knowledge Dependence: 阅读本文前你只需要有基本的几何知识和算法知识(本篇重点要求复杂度分析与选择排序算法)即可.代码实现需要一丢丢C++基础. 由于作者很懒不想画图,所

计算几何-凸包算法 Python实现与Matlab动画演示

凸包算法是计算几何中的最经典问题之一了.给定一个点集,计算其凸包.凸包是什么就不罗嗦了 本文给出了<计算几何——算法与应用>中一书所列凸包算法的Python实现和Matlab实现,并给出了一个Matlab动画演示程序. 啊,实现谁都会实现啦╮(╯▽╰)╭,但是演示就不一定那么好做了. 算法CONVEXHULL(P)  输入:平面点集P  输出:由CH(P)的所有顶点沿顺时针方向组成的一个列表 1.   根据x-坐标,对所有点进行排序,得到序列p1, …, pn 2.   在Lupper中加入p

hdu 1616 计算几何 凸包

题意是一个世界有许多个国家,每个国家有N个建筑,包括一个发电站和N-1个用电建筑,所有建筑围成的凸包就是这个国家的面积.一枚导弹如果在一个国家之内爆炸则可以使这个国家停电. step 1:求出每个国家的凸包(我用水平排序就是各种坑,改叉乘排序才过,主要是后面求面积的时候需要这个叉乘排序的信息). step 2:判断每枚导弹是否在这个国家的范围之内. step 3:求出所有停电的国家的面积. 就是计算几何的综合模拟水题,坑点就是要小心(QAQ||写计算几何的题目都是要小心). 传送门:http:/

计算几何——凸包问题(二)

注:本文是2016年春季清华大学邓俊辉老师<计算几何>MOOC课程的简要个人总结系列之一,我将同步课程内容更新.不过有可能写的不完全是课程内容,也包含一些个人理解.如果你在看完本文后开始对计算几何感兴趣,请前往相应的MOOC平台完整学习邓老师的课程.如此精心设计和编排的课程,不应该被辜负.在此感谢邓老师! Knowledge Dependence:阅读本文前你只需要有基本的几何知识和算法知识即可.代码实现需要一丢丢C++基础. 由于作者很懒不想画图,所以你还需要一点脑内小剧场.不过相信我,自己

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

题目链接: HDU4316 计算几何太duliu了~ 题目大意:空间中有一个物体,上空有\(3\)个摄像头拍摄地面,求摄像头公共盲区面积大小 首先求出每一个摄像头的盲区: 将物体的每一个点投影到地面再求凸包即可. 然后将\(3\)个凸包的每一条边都拿来一起做一次半平面交,求出公共盲区 最后三角剖分求面积就好了. 代码: #include <cmath> #include <cstdio> #include <vector> #include <algorithm&