基于分治法和DCEL数据结构的Delaunay三角剖分

平面点集的 Delaunay 三角剖分简介

以下文字摘自互动百科:   

Voronoi图,又叫泰森多边形或 Dirichlet图,它是由一组由连接两邻点直线的垂直平分线组成的连续多边形组成。 N个在平面上有区别的点,按照最邻近原则划分平面,每个点与它的最近邻区域相关联。 Delaunay三角形是由与相邻 Voronoi多边形共享一条边的相关点连接而成的三角形。 Delaunay三角形的外接圆圆心是与三角形相关的 Voronoi多边形的一个顶点。 Delaunay三角形是 Voronoi图的偶图。
对于给定的初始点集 P,有多种三角剖分方式,其中 Delaunay三角剖分具有以下特征:
  1. Delaunay三角剖分是唯一的;
  2. 三角网的外边界构成了点集P的凸包;
  3. 没有任何点在三角形的外接圆内部,反之,如果一个三角网满足此条件,那么它就是Delaunay三角网。
  4. 如果将三角网中的每个三角形的最小角进行升序排列,则Delaunay三角网的排列得到的数值最大,从这个意义上讲,Delaunay三角网是“最接近于规则化”的三角网。
选题动机: 由于Delaunay三角网是“最接近于规则化”的三角网,与普通的三角剖分相比更加具有实际运用价值,因此实现它比实现普通三角剖分更有意义。

 

实现内容: 本次作业主要实现了平面点集的Delaunay三角剖分。为了验证Delaunay三角剖分的正确性,系统提供了“圆测试”的功能,供用户对生成的三角形进行测试验证;为了验证DCEL数据结构的正确性,系统提供了“DCEL测试”的功能,用户输入任意一条测试线,系统将先后显示出测试线贯穿过的三角形;为了形象显示三角剖分的分治过程,系统提供了动态显示分治剖分过程。

报告组成: 首先对算法依赖的数据结构DCEL进行说明,然后对基于DCEL的分治算法进行介绍,接着介绍DCEL测试和圆测试的相关细节,最后是对作品操作进行介绍,以及展示和分析实验结果。

数据结构与操作:Quad-Edge vs DCEL

Guibas等[1]在文章中提出了一种Quad-Edge结构,使得Delaunay三角剖分的操作过程变得比较简单,并且由于这种结构保存了边的对偶信息,因此Delaunay三角与Voronoi diagrams之间的相互转换也非常容易。

此次实验要求使用DCEL(Doubly-Connected Edge List)结构,该结构比Quad-Edge简单,并未保存边的对偶信息。下面的两幅图显示了两种数据结构在信息存储上的区别。

Quad-Edge中的next为以origin为起点,逆时针找到的e[0]的下一条边。

虽然DCEL没有存储Quad-Edge中的对偶边e[1]和e[3],可能对从Delaunay三角转化为Voronoi图有所不便,但是对于实现Delaunay三角剖分来说DCEL已经足够用了,用Quad-Edge可以找得到的与e[0]相关联的边,使用DCEL结构也可以很轻易的找到,下面给出这些关联边的图示。

几个重要的函数操作

1.void splice(Edge* a, Edge* b)

该函数的功能为:若a和b的起点是一个点,那么 a和 b将会被分成两条不相干的边;若 a和 b原本是两条不相干的边,那么这两条边的起点就会被连起来。下面是 Guibas文章的中一张对 splice进行解释的图:
DCEL中 splice的具体实现与 Quad-Edge略有不同,只要掌握了 splice具有上述功能的本质原因,就能很容易的实现了。具体实现请参考源码 dtriangulation。

2.Edge* connect(Edge* a, Edge* b)

该函数的功能为:把 a的终点与 b的起点连起来。

3.void swap(Edge* e)

该函数的功能为:在 inCircle*测试失败后(意味着与 e相邻的两个三角不是 Delaunay三角),从 e的起点、终点沿四边形内部逆时针找到下两个点作为新的起点、终点,添加边 e‘,并删除边 e。如图所示:

*inCircle(a,b,c,d)函数测试点d是否在点a、b、c构成的三角形的外接圆内部。

判定一个点是否在一个圆内,我们马上想到得算法是求出该点到圆心的距离,然后与圆的半径大小做比较,但是假设圆心事先不知道,只提供了该圆上的三个顶点,然后同过这三个顶点来求得圆心的位置,我们马上想到的办法也可能是求出两条连线垂直平分线的交点就是圆心,但是这种算法在三点共线的情况下,两条连线的垂直平分线的交点在无穷远处,计算机是无法记录该圆心坐标,因此这种算法不鲁棒。 文献 [1]中提到的 InCircle方法是把平面上的点(x,y,0)投影到z=x 2+y 2的抛物面上,得到相应的投影坐标为(x, y, x 2+y 2),如下图所示:

 

判定原则如下:
  • 如果d的投影在a、b、c投影所组成平面的上方,则d在a、b、c构成的三角形的外接圆外部。
  • 如果d的投影在a、b、c投影所组成平面内,则d在a、b、c构成的三角形的外接圆的边界上。
  • 如果d的投影在a、b、c投影所组成平面的下方,则d在a、b、c构成的三角形的外接圆的内部。

4.Edge* locate(const Point2d& p, Edge* startEdge)

该函数的功能是给定一个点p,从startEdge出发,找到该点所落在的Delaunay三角剖分面片,返回该三角面片的按ccw方向的一条边。
主要步骤如下(见下图):
  1. 首先判断该点是否落在三角剖分凸包外,如果落在外面则返回一个空边,算法结束,否则转到步骤2。
  2. 令e=startEdge,如果p落在e上,则返回e,算法结束,否则转到步骤3。
  3. 如果p点不是在e.onext的右边,则e=e.onext,转到步骤2,否则转到步骤4。
  4. 如果p点不是在e.dprev的右边,则e=e.dprev,转到步骤2,否则返回e,算法结束。

 

分治法 Divide-and-Conquer

主要步骤

  1. 排序和去重:对平面上所有点按x坐标非递减排序进行排序,对于x坐标相同的点,再按y坐标非递减排序,然后去掉坐标重复的点。通过预排序,分治法可以在常数时间内完成划分;
  2. Divide:把平面上的点集按数量在中间分成左右两个子集 L  R 
  3. Conquer:递归地完成左右两个点子集 L  R Delaunay三角剖分;
  4. Merge:把 L  R 两个点集的Delaunay三角剖分,合并成( L U R )点集的Delaunay三角剖分。

详细说明

递归划分结束条件

当平面上的点集划分到数目  N 小于4时(显然只会出现节点为2和3的情况,不会出现为1的情况),直接两两点互连就是Delaunay三角剖分,递归划分结束(具体情况见下图)。

递归返回数据结构

由于 DCEL结构的特性,可以使得剖分完成后的所有边都可以通过任意边抵达,本递归算法返回结果是两条边,分别是:以最左点为始点的逆时针方向的边和以最右点为始点顺时针方向的边(见下图)。
保存这两条特殊的边的目的:
  • 通过其中任意一条边访问到子Delaunay剖分上的任意一条边;
  • 能最速找到 L  R 两部分三角剖分凸包的下公共切线。

下公共切线寻找算法

两个子凸包的下公切线查找使用的是 Zig-Zag算法,具体的细节可参考 [1]或是邓俊辉老师计算几何课件。

Merge具体步骤

  1. 找到左右Delaunay三角剖分的下公切线,从右边指向左边,记为basel,且basel一定为合并后的Delaunay三角剖分中的一条边;
  2. 以basel的左端点为始点,以某个候选左邻居点为终点的边记为lcand,类似的,以basel的右端点为始点,以某个候选右邻居点为终点的边记为rcand。
  3. 先看lcand的终点lcand.dest是否在basel的右边,如果是,则测试经过basel.dest、basel.org、lcand.dest三个顶点的外接圆是否包含顶点lcand.onext.dest,如果包含,则说明lcand不是Delaunay三角剖分的一条边,删除lcand,且令lcand=lcand.onext,重复进行步骤3。针对于rcand要进行与lcand类似的操作。见下图。
  4. 如果lcand的终点lcand.dest和rcand的终点rcand.dest都不是在basel的右边,那说明basel是上公切线,Merge算法终止。
  5. 如果lcand.dest不在basel的右边或者rcand.dest在basel的右边而且rcand.dest包含在经过lcand.dest、lcand.org、rcand.org三个顶点的外接圆内,则连接顶点rcand.dest和basel.dest的边一定是合并后的Delaunay三角剖分中的一条边且令basel指向该边,否则,则说明连接顶点basel.org和lcand.dest的边为合并后的Delaunay三角剖分的一条边且令basel指向该边。转到步骤2。

时间复杂度分析

  •  L 三角剖分后返回的两条边为ldo,ldi, R 三角剖分后返回两条边为rdi,ldo,通过ldi与rdi用zig-zag算法,Merge步骤1中的找下公切线能在O(n)内完成。
  • Merge步骤3中执行的次数等于lcand边删除的次数,而Delaunay三角剖分边的数目与顶点的数目呈线性关系,因此最多是把所有的边删除,因此Merge步骤3执行的次数为O(n)。
  • Merge步骤4、5执行的次数等于新添加的连接左右两边顶点的cross边的数目,cross边的数目与顶点的数目呈线性关系,因此Merge步骤4、5执行的次数为O(n)。
分治法的Divide的时间复杂度为O(1),Merge的过程时间复杂度为O(n),设总的时间复杂度为T(n),则T(n)=2T(n/2)+O(n),根据主定理可以推出该分治法的时间复杂度T(n)为O(nlogn)

DCEL 测试

DCEL测试是通过在Delaunay三角剖分中连接一条直线  l ,然后按顺序找到并记录该边穿过的三角面片。主要是为了验证DCEL数据结构的正确性和点定位 locate算法的效率(见下图)。

主要步骤如下

  1. 调用locate函数看直线 l 的始点p1与终点p2是否在Delaunay三角剖分所组成的凸包内。如果p1在凸包外,在顺时针沿着凸包的边界检查 l 是否和边界线相交,如果 l 不与任何边界线相交,则说明 l 不穿过Delaunay三角剖分中的任意三角面片,否则转到步骤2。如果p1在某个三角面片tri内,则逆时针围绕该tri的边找寻是否有与 l 相交的边,如果 l 不与任何边相交,则说明 l 完全位于tri的内部,算法结束,否则转到步骤2。
  2. 记与 l 相交的边为e1,保存e1,接着找到el的twin:e2,然后从e2出发,逆时针在e2的左三角面中找与 l 相交的边,如果找到了相交的边,重复步骤2,否则就说明终点p2在该三角形中或p2在凸包外,算法终止。

圆测试

Delaunay三角剖分具有如下性质:任一三角形外接圆内部不会出现第四个点。

为了直观地检测该程序对平面点集进行Delaunay三角剖分的正确性,我们为该程序添加了圆测试功能。在三角剖分之后,点击“操作”->“外接圆测试”,点中屏幕中任意一个三角形,程序将会绘制出该三角形的外接圆(左图),如果有第四个点“仿佛”落在该圆内,可以使用放大镜功能将该区域局部放大(中图),可以很明显地看出该点不在圆内(右图)。

  

遇到的问题:刚开始使用垂直平分线交点的方法求解三角形外接圆,后来发现忘记判断三角形的边是否水平或者竖直,导致在某些情况下无法计算得出外接圆。之后偶然发现一种更好的求解外接圆的方法[4],该方法简化了表达式,不需要繁琐的判别,就可求出结果,因此我们也采用该方法求解外接圆。具体实现请参考源码dtriangulation。

作品界面和使用说明

界面整体印象

用户手册

  • (或者“文件”->“新建”):将已有数据和界面图案进行清空,重新初始化。
  • (或者“文件”->“打开”):打开和读入保存在文件中的点集数据。此为输入点集方式之一。
  • (或者“文件”->“保存”):保存界面中显示的点集数据到文件中。
  •  :放大显示状态。当界面显示的点集数据量较大情况下,为了观察某部分细节情况,先点击此图标,然后用鼠标左键按住选择界面中感兴趣的部分,松开鼠标左键后,界面即可显示出选择的部分细节。
  •  :还原显示状态。当界面在显示局部细节的状态下(上一条操作后),系统约束不能进行其他操作。需要在点击此图标后,解除约束,才能进行其他操作。
  • “操作”->“随机生成点”:输入点集数据方式之一。在弹出的对话框中输入点数,界面将显示出此数目的点集。
  • “操作”->“鼠标输入点”:输入点集数据方式之一。进行此选择后,可以使用鼠标在界面上点击先后输入一些列点集。
  • “操作”->“Delaunay三角剖分”:在界面上显示点集的情况下,进行此选择后,将进行三角剖分操作,并将结果显示在界面上。界面的右下栏中显示剖分时间。
  • “操作”->“DCEL测试”:在完成三角剖分操作后,进行此选择,然后在界面上按下鼠标左键,然后拖动到另外一个地方,松开鼠标后,界面上将先后显示出此线段贯穿过的三角形。
  • “操作”->“外接圆测试”:在完成三角剖分操作后,进行此选择,然后在界面上的任意三角形上进行点击,界面上将显示出此三角形的外接圆。
  • “操作”->“三角剖分分治演示”:在输入点集的情况下,进行此选择,界面上将显示分治剖分的过程演示。
  • 快捷键“Enter”:即时随机生成一组当前设置点数的点集剖分结果图。界面的右下栏中显示剖分时间。此操作可以快捷地测试相同数目的不同组点集剖分结果。

实验结果展示

不同数量级的点集显示

下面左图是1k个点的显示,右图是100k个点的显示。

不同数量级的点集剖分结果

下面两个剖分结果是分别对应上面两组输入的点集。

剖分结果的DCEL测试

下面四组图是对剖分三角形进行DCEL测试的结果图。动态演示过程请见下面的视频demo。

剖分结果的圆测试

系统提供了对剖分三角形进行圆测试,验证剖分得到的三角形是否正确。圆测试结果如下。

显示细节

在输入的点集数目达到10k以上,比较难分辨出点和线的细节信息,此时可以通过选择放大显示来观察相关细节部分情况。如下图所示,左边是整体的显示,右边显示左边被框住区域的细节。

分治过程演示结果

系统支持对分治过程的中间结果进行演示,由于程序在演示过程中无法进行截屏,演示过程见下面的视频demo。

视频demo

下面的视频对以上涉及的相关操作都有一个简单的演示过程。具体情况可以下载并观看此视频。

http://cgprojects09.googlecode.com/files/demo.avi

算法性能测试

  • 时间复杂度分析 分治的三角剖分算法时间复杂度是 O( n log( n ))。通过实验数据可以验证之。下面是相关实验数据(每一组数据是通过测试10次,取其平均值作为耗时)。

 

点集数目 100 500 1,000 5,000 10,000 50,000 100,000 500,000 1,000,000 2,000,000 3,000,000 4,000,000 5,000,000
算法耗时(us) 184 1,293 2,922 18,153 39,334 228,714 485,895 2,723,544 5,777,429 12,061,211 18,188,295 24,803,120 32,020,173

 

用上述实验数据进行曲线拟合,可以得到下面一条曲线,满足O( n log( n ))的性质。

  • 空间复杂度分析 大致分析算法的空间复杂度为O( n ),通过实验数据也可以验证这一观点。下面是相关实验数据(每一组数据是通过测试10次,取其平均值作为结果)。

 

点集数目 100 500 1,000 5,000 10,000 50,000 100,000 500,000 1,000,000 2,000,000 3,000,000 4,000,000 5,000,000
内存消耗(M) 5 6 12 13 14 20 27 90 168 334 482 638 850

 

使用上述数据进行曲线拟合,大致可以看出,在点集数目为0.5M - 5M之间,曲线大致是线性变化的(点集数目较小时不能说明问题,因为算法使用的数据结构所占内存基本不占主要成分)。

以上测试实验的环境为:Intel Core 2 Duo CPU 2.2GHz, 2.00GB RAM。

参考文献

[1]L. Guibas and J. Stolfi, Primitives for the manipulation of general subdivisions and the computation of Voronoi diagrams, ACM Transactions on Graphics, 4(2):75-123, 1985.

[2]R. A. Dwyer, A faster divide-and conquer algorithm for constructing Delaunay triangulations algorithmica, 2:137-151, 1987.

[3]D. Lischinski, Incremental Delaunay triangulation, Graphics gems IV, 47-59. 1994.

[4]段小武,巧求外接圆圆心,电脑开发与应用,第15卷,第08期,2002年。

其他说明

未来工作

  1. 界面上对点线显示的缩放和移动操作。目前系统只实现了对局部进行放大显示,更加方便的交互式操作可以引入连续的缩放和移动操作。

缩写说明

  • DCEL:双向链接边表(Doubly-Connected Edge List)。
  • us: 微秒,10^(-6)秒。

测试数据

数据文件格式:

5 # 第一行数字为点集个数

0.1 0.2 # 第二行以下是点集的信息,x坐标和y坐标

0.3 0.5

0.4 0.9

1.0 2.0

6.0 2.0

  • 10,000个点的普通测试点集:http://cgprojects09.googlecode.com/files/10000.point
  • 1,000,000个点的普通测试点集:http://cgprojects09.googlecode.com/files/1000000.point
  • 含有重合点的测试点集:http://cgprojects09.googlecode.com/files/badpoint1.point
  • 在同一直线上的测试点集:http://cgprojects09.googlecode.com/files/badpoint2.point

你可能感兴趣的:(Algorithm,数据结构,算法,测试,merge,transactions)