2016年12月07日 09:33:36 fjssharpsword 阅读数:3236更多
所属专栏: 算法导论专栏
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fjssharpsword/article/details/53501684
计算几何学是计算机科学的一个分支,专门研究集合问题的解决的算法。计算几何学的问题一般输入关于一组集合对象的描述,如一组点、一组线段;输出是对问题的回答,如直线是否相交。三维空间和高维空间很难视觉化,这里计算几何学主要基于二维平面,输入对象用一组点
1)线段的性质
两个不同的点p1=(x1,y1)和p2=(x2,y2)的凸组合是满足下列条件的任意点p3=(x3,y3):对某个a(0≤a≤1),有x3=ax1+(1-a)x2,y3=ay1+(1-a)y2,即p3=ap1+(1-a)p2。p3是位于穿过p1和p2的直线上、并处于p1和p2之间(包括p1和p2两点)的任意点,在给定两个不同的点p1和p2的情况下,线段p1p2是p1和p2的凸组合(convex combination)的集合。p1和p2是线段p1p2的端点,并需要考虑p1和p2之间的顺序。
在回答计算集合线段性质上,一般选择加法、减法、乘法和比较运算,避免使用除法和三角函数,因为这二者计算代价高昂且容易产生舍入误差降低精度。
叉积:
叉积是关于线段算法的中心,定义为p1Xp2,是由点(0,0)、p1、p2、和p1+p2=(x1+x2,y1+y2)所形成的平行四边形的面积,也等价于一个矩阵的行列式:
如果p1Xp2为正数,则相对于原点(0,0)来说,p1在p2的顺时针方向上;如果为负数,则p1在p2的逆时针方向上;如果为0,即在边界,则两个向量是共线的,指向同一个方向或相反的方向。
要确定公共端点p0,有向线段p0p1是否在有向线段p0p2的顺时针方向上,可计算叉积:
(p1-p0)X(p2-p0)=(x1-x0)(y2-y0)-(x2-x0)(y1-y0)
如果该积为正,则向线段p0p1在有向线段p0p2的顺时针方向上;如果为负,则向线段p0p1在有向线段p0p2的逆时针方向上。
连续线段的转向判断:
问题是在点p1处,两条连续的线段p0p1和p1p2是向左转还是向右转,就是确定p0p1p1的角的转向。应用叉积,检查有向线段p0p2是在有向线段p0p1的顺时针方向还是逆时针方向即可判断。计算叉积(p2-p0)X(p1-p0),为负,则p0p2在p0p1的逆时针方向,在p1点会左转;如果为正,则p0p2在p0p1的顺时针方向,在p1点会右转。
两个线段的相交判断:
要确定两个线段是否相交,通过每个线段是否跨越了包含另一条线段的直线。给定一个线段p1p2,如果点p1位于某一直线的一边,而点p2位于该直线的另一边,则称线段p1p2跨越了该直线。如果p1和p2就落在该直线上的话,就是出现边界情况。
两个线段相交,当且仅当下面两个条件中的一个成立,或同时成立:
第一:每个线段都跨越包含了另一个线段的直线;
第二:一个线段的某一端点位于另一线段上(边界情况)。
算法伪码上主要是两个过程:第一个过程是计算每个端点相对于另一条线的方位,就是计算三个点的转向判断,得出相对方位d;第二个过程对d非零和零进行判断。
2)确定任意一对线段是否相交
要确定任意一对线段是否相交,采用扫除技术,算法运行时间是O(nlgn),其中n是已知的线段数目。扫除技术只确定是否存在相交的线段,并不输出所有的相交点。扫除算法的过程,是假设存在一条垂直扫除线,自左向右依次穿过已知的几何物体,如果从x轴方向来看,就是一组垂直的线沿着时间横扫。扫除技术有两点假设:第一假定没有一条输入线是垂直的;第二假设没有三条输入线相交于同一点。扫除过程中,根据扫除线和线段相交的交点的y坐标值进行排序。对一个一条线段来说,扫除线从线段的左端点开始到右端点离开,过程中y的值随着x的变化而变化,从而有不同的排序。这个很好理解,两条线段的y值在前后扫除线的不同时间点具有不同的排序(大小出现颠倒),则说明有相交。
扫除算法维护两组数据:第一扫除线状态,记录了与扫除线相交的线段之间的关系;第二事件点调度,记录从左到右的x坐标的序列。
扫除线状态是一个全序T,用一颗红黑树实现,可在T上执行四个操作:
Insert(T,s),将线段s插入到T中。
Delete(T,s),将线段s从T 中删除。
Above(T,s),返回T中紧靠线段s上面的线段。
Below(T,s),返回T中紧靠线段s下面的线段。
算法的伪码描述就不具体展开,算法输入n个线段组成的集合S,如果S中有任意一对线段相交,则输出TRUE布尔值。算法整个过程通俗的理解就是:一条垂直的线沿着x轴自左向右扫过去,S集合中的线段在扫除线的每个事件点(x轴上的点)对线段y值进行排序。导论中图示表示很清晰,也做了证明。
3)寻找凸包
点集Q的凸包(convex hull)是一个最小的凸多边形P,满足Q中的每个点或者在P的边界上,或者在P的内部,用CH(Q)表示凸包。计算包含n个点的点集的凸包,有两种都是按照逆时针方向顺序输出凸包的各个顶点,一个是Graham扫描法,运行时间O(nlgn);还有一个是Jarvis步进法,运行时间是O(nh)。直观上说,找出凸包的顶点就可以,两种算法都运用了旋转扫除技术,根据每个顶点对一个参照顶点的极角大小,依次处理。思路上就是选取参照点,计算凸包中和参照点的关系。
凸包问题和计算几何中的最远点对问题也相近,已知平面上的n个点的集合,找出他们中距离最远的两个点。还有其他算法,如增量算法、分治法、剪枝-搜索法。
Graham扫描法:
Graham扫描法通过设置一个候选点的堆栈S来解决凸包问题。输入集合Q中的每个点都被压入栈一次,非凸包CH(Q)中顶点的点被弹出堆栈,算法终止时,堆栈S中仅包含CH(Q)中的顶点,其顺序为各点在边界上出现的逆时针方向排列的顺序。
具体伪码和案例及其正确定证明不描述,主要说下算法过程:首先选择参照点p0,一般选择顶点(最下边或最右边这种);其次将所有点按照和p0极角大小递增顺序压入堆栈;接着依次弹出右转的非凸包顶点,最后剩下就是凸包的顶点。
Jarvis步进法:
Jarvis步进法采用打包(package wrapping)技术来计算一个点集Q的凸包,算法运行时间为O(nh),其中h是CH(Q)的顶点数。
该方法的思路可以这样理解:找出Q集合中的最低点p0(y值最小),这是凸包的一个顶点,以该点为基础放射出一条无限长的线段,从右边开始沿上扫描,直到碰到一个点(遇到的点都是顶点),直到360度旋转回p0点。
这个过程中最重要的是,扫描过程中界定遇到的点是顶点,理论上,找出具有最小极角的点,如果极角相同再找出距离最远的点(x轴的距离)。
4)寻找最近点对
在n≥2个点的集合Q中寻2找最近点对的问题。最近通常理解为欧几里得距离,点p1=(x1,y1)和p2=(x2,y2)之间的距离为:
如果集合中两个点重合,那距离就是0。寻找最近点对问题应用在空中或海洋交通控制系统中,用于发现两个距离最近的交通工具,以便检测出可能发生的相撞事故。
一般简单的做法,就是计算出所有点的两两距离,然后得出最近点对,这个算法是点数n的平方时间性能。如果应用分治算法,采用递归T(n)=2T(n/2)+O(n),算法运行时间为O(nlgn)。下面描述分治算法的思路。
分治算法的每一次递归调用输入为子集P⊆Q和数组X和Y,每个数组均包含输入子集P的所有点。数组X中点,按x坐标单调递增排序;同样的,数组Y中的点,按y坐标单调递增排序。递归退出在检查|P|≤3,如果小于等于3个点,那直接就两两比较;如果大于3,则接着分治。具体步骤:
第一分解:找出一条垂直线L,将点集P划分为左右两个集合PL和PR,需要满足二者的点数是P的二分之一。自然,数组X就也划分两个数组XL和XR,数组Y也 划分为两个数组YL和YR。
第二解决:P划分成PL和PR后,进行两次递归调用,一次找出PL的最近点对,返回最近点对距离l,一次找出PR的最近点对,返回最近点对距离r;取min(l,r)返回。
第三合并:最近点对,要么是某次递归调用返回的取min(l,r)距离,要么就是 PL中的一个点和PR中的一个点组成点对,算法中需要确定跨数组的点对距离和min(l,r)距离关系;如果小于,则点对中的两个点一定在距离垂直线L的min(l,r)单位内;这样就建立一个以垂直线L为中心,宽度为2 min(l,r)的垂直带形区域内;找出这样点对,需要对数组Y内不在该区域的点去掉,剩下依旧按照y坐标顺序排序;对数组Y中在区域内的所有点,找出距离其在min(l,r)单位距离内的点,并记录下其点对距离;如果存在比min(l,r)更小的,则返回该点及其距离。
算法的正确性就不多叙述,其实计算几何需要抽象去理解,好在二维的还是比较好理解。