11.1 引言
一、二维平面中的点、线段和多边形
1、点:
用数偶(x,y)表示点p的坐标。 2、线段:
由它的两个端点表示。
如果p(x1,y1),q(x2,y2)是两个离散的点,端点为p和q的线段,表示为pq。 3、多边形:
多边形路径是点p1,p2,,pn的一个序列,其中,pipi1是线段,1in1。 如果p1pn,则由所封闭起来的区域称为多边形。
pi称为多边形的顶点;线段pipi1称为多边形的边。
通常用“多边形”来表示多边形的边界。 4、简单多边形和非简单的多边形:
除了顶点之外,任何两条边都不会交叉的多边形称为简单多边形; 有交叉边的多边形称为非简单的多边形。
例:图11.1(a)是简单的多边形,图11.1(b)是非简单的多边形。
(a) (b)
图11.1 简单的多边形(a)和非简单的多边形(b)
5、凸多边形和非凸多边形
凸多边形:连接多边形任意两个顶点的线段,完全处于多边形内部, 非凸多边形:连接多边形任意两个顶点的线段,不完全处于多边形内部 例:图11.2(a)表示一个凸多边形,图11.2(b)表示一个非凸多边形。
1
(a) (b)
图11.2 凸多边形(a)和非凸多边形(b)
二、有向面积
1、以源点作为始点的两个向量的有向面积
点p(x1,y1),q(x2,y2)是以源点o(0,0)作为始点的两个向量op及oq的端点,则向量op及oq的有向面积为:
opoqx1y1x2 y2x1y2x2y1 oqop
2、两向量有向面积的平行四边形表示
若过点p平行于oq的直线与过点q平行于op的直线相交于s, p,q,s的垂线与X轴分别相交于a,b,c,
平行四边形opsq的面积S为:
obq的面积加上梯形qbcs的面积,减去opa的面积,再减去梯形pacs的面积。
显然,acobx2,bcoax1,csbqapy1y2。
S1111x2y2(y2y2y1)x1x1y1(y1y1y2)x2 2222 x1y2x2y1
y s q(x2,y2) p(x1,y1) o b a c x
图11.3 由两个向量所构成的平行四边形
3、结论
1)opoq的有向面积的绝对值,等于平行四边形opsq的面积。
2
2)当opoq值为正时,向量op可沿着平行四边形内部逆时针旋转到达oq; 3)当opoq值为负时,向量op可沿着平行四边形内部顺时针旋转到达oq。 4、左转的三角区和右转的三角区
o,p,q三点的坐标分别为(x1,y1),(x2,y2),(x3,y3), op(x2x1,y2y1),oq(x3x1,y3y1)。
opoq(x2x1)(y3y1)(x3x1)(y2y1)
x1y2x2y3x3y1y1x2y2x3y3x1
上述opoq的有向面积,也可用下面的行列式的值来确定:
x1Dx2x3y1y2y311x1y2x2y3x3y1y1x2y2x3y3x1 1(11.1.1)
当D为正时,o,p,q,o构成一个反时针方向的回路,就说路径o,p,q是左转的, 当D为负时,o,p,q,o构成一个顺时针方向的回路,就说路径o,p,q是右转的, 当D0时,这三点在同一直线上。
例:图11.4表示左转的三角区和右转的三角区。
q q p p o o (a) (b)
图11.4 左转的三角区域(a)和右转的三角区域(b)
二、几何扫描
1、几何扫描
对物体进行扫描,以便确定物体的几何形状、识别物体中各个部件的几何特征及部件之间的联系。 2、平面几何扫描
在二维平面上,对某一个对象从左到右用垂直线进行扫描。
三、平面扫描算法的基本组成部分:
1、事件调度点p_schedule
按X坐标排序的点序,由这些点定义了扫描线的“站点”位置。 2、扫描线状态,
扫描线的“站点”状态,表示在“站点”位置时,所处理几何对象的状态。 扫描线状态的描述,取决于所处理的几何对象。
3
11.2 平面线段的交点问题
一、问题
给定平面上n条线段的集合L{l1,l2,,ln},寻找它们的交点集合。 二、平面上线段的关系描述
定义11.1 令li和lj是平面上任意两条线段,它们与X坐标为x的垂线分别相交于点pi与pj。若pi的Y坐标值大于pj的Y坐标值,就说li在x高于lj,记为lixlj。
如果线段li和lj,其中有一条、或者两条都不与X坐标为x的垂线相交,就说这两条线段不存在x关系。
例:图11.5中,有如下关系成立:
l1al3al4,l3bl1bl4,l2cl3
在“站点”a,扫描线状态为l1al3al4; 在“站点”b,扫描线状态为l3bl1bl4; 在“站点”c,扫描线状态为l2cl3。
l1 l2 l3 l4 a b c x
图11.5 线段之间的x关系
11.2.1 寻找平面线段交点的思想方法
一、寻找平面线段交点的两种方法
1、计算所有的线段的交点,需O(n2)时间。
2、从左到右扫描所有线段,根据线段的x关系,对扫描到的线段进行定序,排除不可能相交的线段,只对有可能相交的线段确定它们的交点位置,可以有效减少计算交点的时间。 二、算法的思想方法:假定不会出现三条线段相交于一点的情况
对n条线段的2n个端点以X坐标的非降顺序排序; 用垂线从左到右扫描所有的线段;
用线段的端点和交点构成事件调度点序列E; 把扫描线所扫描到的线段按x关系定序, 在扫描线上线段的x关系,构成了扫描线的状态, 开始时,扫描线的初始状态为空,
扫描线从左到右扫描时,遇到三种事件调度点:线段的左端点、右端点、两线段交点。
4
每遇到一个事件调度点,就执行下面的一种相应的动作,并刷新扫描线的状态: 1. 扫描到线段l的左端点的处理:
把线段l按照x关系的顺序,插入到当前扫描线状态集S中, 若S中存在与l紧邻的线段l1以及(或者)l2,使得l1xl,lxl2, 且若l与l1,以及(或者)l与l2有交点,把交点保存到交点集T中, 把交点按X坐标的非降顺序,插入事件调度点序列E中; 2. 扫描到线段l的右端点的处理:
若S中存在与l紧邻的线段l1及l2,使得l1xl,lxl2,
并且,如果l1与l2有交点且未处理,就把它们的交点保存到交点集T中, 并把它们的交点按X坐标的非降顺序,插入事件调度点序列E中, 把线段l从S中删去;
3. 扫描到线段l1及l2的交点的处理:
把扫描线状态集S中l1与l2的x关系颠倒过来,
在交点的右边,如果S中存在着l1与l2的紧邻l3及l4,使得l3xl2,l1xl4, 并且l3与l2、l1与l4有交点,且交点尚未保存到T中,就保存它们的交点, 把交点按X坐标的非降顺序,插入事件调度点序列E中。 例11.1 寻找图11.6中五条线段的交点。
10个端点按X坐标的非降顺序,事件调度点序列E{1,2,3,4,5,6,7,8,9,10}。 用垂线从左到右扫描事件调度点,过程如下:
2 1 4 a b 9 c 7 e 10 3 d f 5 6 8 图11.6 寻找线段交点的过程
1. 端点1: S{l1};T;E{2,3,4,5,6,7,8,9,10}; 2. 端点2: S{l2,l1}; T{a};E{3,4,5,a,6,7,8,9,10}; 3. 端点3: S{l2,l1,l3};T{a};E{4,5,a,6,7,8,9,10} 4. 端点4: S{l2,l4,l1,l3};T{a,b},E{b,5,a,6,7,8,9,10}; 5. 交点b: S{l2,l1,l4,l3};T{a,b,c};E{5,c,a,6,7,8,9,10}; 6. 端点5: S{l2,l1,l4,l3,l5};T{a,b,c};E{c,a,6,7,8,9,10}: 7. 交点c: S{l2,l1,l3,l4,l5},T{a,b,c,d};E{a,d,6,7,8,9,10}; 8. 交点a: S{l1,l2,l3,l4,l5};T{a,b,c,d,e};E{d,6,e,7,8,9,10}; 9. 交点d: S{l1,l2,l3,l5,l4},T{a,b,c,d,e};E{6,e,7,8,9,10}; 10. 端点6: S{l1,l2,l3,l5};T{a,b,c,d,e};E{e,7,8,9,10};
5
11. 交点e: S{l1,l3,l2,l5};T{a,b,c,d,e,f};E{7,f,8,9,10}; 12. 端点7: S{l1,l2,l5};T{a,b,c,d,e,f};E{f,8,9,10}; 13. 交点f: S{l1,l5,l2};T{a,b,c,d,e,f};E{8,9,10}; 14. 端点8: S{l1,l5};T{a,b,c,d,e,f};E{9,10}; 15. 端点9: S{l5};T{a,b,c,d,e,f};E{10}; 16. 端点10:S;T{a,b,c,d,e,f};E;
11.2.2 寻找平面线段交点的实现
一、数据结构
1、有关线段的数据结构:
typedef struct {
float x; float y; } POINT;
typedef struct {
POINT lp; POINT rp; } LINE; LINE BOOL
line[n]; b[n][n];
/* n条线段 */
/* n条线段的相交标志 */
/* 线段的左端点 */ /* 线段的右端点 */
/* 点的X坐标 */ /* 点的Y坐标 */
2、有关事件调度点的数据结构:
typedef struct {
float x; float y; int int int } HEAP; HEAP HEAP
p;
/* 事件调度点 */
/* 存放事件调度点的堆存储空间 */
tag;
/* 事件调度点的X坐标 */ /* 事件调度点的Y坐标 */
/* 事件调度点标志:=0 左端点,=1 右端点,=2 线段交点 */
line1; /* tag=0,1时的线段序号,tag=2时,交点左边高顺序线段序号*/ line2; /* tag=2时,交点左边低顺序线段序号*/
heap[3*n];
3、有关扫描线状态的数据结构:假定,线段按照序号顺序存放在数组line
struct tree {
int
line;
/* 线段序号 */
6
int high; /* 结点子树的高度 */ /* 左儿子结点 */ /* 右儿子结点 */
struct tree *lchild; struct tree *rchild; };
typedef struct tree TREE; TREE
*s;
/* 平衡二叉树的根结点指针 */
二、数据结构的有关操作
1、用最小堆存放事件调度点序列,使用下面两个堆的操作:
void insert(HEAP heap,int &n,HEAP p); 把事件调度点p插入堆中
HEAP delete_min(HEAP heap,int &n); 返回X坐标最小的点,并从堆中删去 很清楚,这两个操作以O(logn)时间维护堆的数据。
2、用平衡二叉树存放扫描线状态,使用下面维护树的衣服操作,及线段交点的计算: TREE *insert_t(int line,TREE *s,float x); 把序号为line的线段插入S中,返回指向 void delete_t(TREE *line,TREE *s); int above_t(TREE *line,TREE *s); int below_t(TREE *line,TREE *s);
S中line的指针
从S中删去指针line所指向的线段
TREE *search_t(int line,TREE *s,float x); 在X坐标上检索序号为line的线段
返回当前X坐标上高于line的线段序号 返回当前X坐标上低于line的线段序号
判断并计算两条线段的交点
BOOL inter_sec(int line1,int line2,POINT &p); 三、有关计算式
1、确定线段在S中的插入位置
已知:欲插入S的线段左端点坐标(x0,y0),x0x
S中被检索线段的两端点坐标(x1,y1)及(x2,y2) 求:S中的线段在x处的Y坐标值y
目的:y0与y比较,以确定被插入线段与S中的线段的x关系 由平面解析几何知,有直线方程:
yy1y2y1(xx1) x2x1 (11.2.1)
S中被检索线段的Y坐标值y为:
yy2y1(xx1)y1 x2x1 (11.2.2)
2、线段交点的计算 已知:线段两端点 求:线段交点
7
(11.2.1)式改写为
(y2y1)x(x2x1)y(x2x1)y1(y2y1)x10
令:
Ay2y1,B(x2x1) ,CAx1By1 (11.2.3)
则上述直线方程可写成:
AxByC0
(11.2.4)
若已知直线l1及l2的联立方程为:
A1xB1yC10 AxByC0222 (11.2.5)
解此联立方程,得:
xB1C2B2C1
A1B2A2B1yC1A2C2A1
A1B2A2B1(11.2.6)
由(11.2.6)式,可得两直线的交点坐标。
当交点坐标位于其中一条线段的两个端点坐标之外,则这两条线段没有交点。 当A1B2A2B10时,这两条线段平行,也没有交点。
四、求取平面上n条线段的交点集合步骤:
1. 按X坐标的非降顺序把线段两端点,放入最小堆heap中;T{},L{},S{}; 2. 如果heap为空,算法结束;否则转3;
3. 取下heap的最小元素于p,如果p是线段l的左端点,转4;否则,转6; 4. 按(11.2.2)式调用insert_t(l,S,x)操作,把线段l插入扫描线状态S中; 5. 令l1above_t(l,S),l2below_t(l,S); 若l与l1有交点,计算该交点q1, 若l与l2有交点,计算该交点q2,
把q1与q2插入heap;TT{q1,q2};LL{{l,l1},{l,l2}};转2; 6. 如果p是线段l的右端点,转7;否则,转8;
7. 令l1above_t(l,S),l2below_t(l,S),从S中删去l,
若l1与l2有交点,计算该交点q,把q插入heap;TT{q};LL{{l1,l2}};转2;
8. p是线段l1和l2的交点,l1在p的左边高于l2; 令l3above_t(l1,S),l4below_t(l2,S); 交换S中l1和l2的位置;
若l2与l3有交点,计算该交点q1, 若l1与l4有交点,计算该交点q2,
把q1与q2插入heap;TT{q1,q2};LL{{l2,l3},{l1,l4}};转2;
8
五、算法描述:
算法11.1 求取平面上n条线段的交点集合 输入:线段line[],线段数目n
输出:交点集合T[],相交的线段L[]个数k
1. void intersections(LINE line[],int n,POINT T[],KINE_PAIR L[],int &k) 2. {
3. int i,j,k,m,temp; 4. HEAP p,p1,heap[3*n]; 5. TREE *s,*snode; 6. LINE line_1,line_2; 7. BOOL b[n][n] = {FALSE}; 8. m = 0; k = 0; 9. s = new TREE;
10. s->lchild = s->rchild = NULL; /* 扫描线状态置为空*/ 11. for (i=0;i 12. p.x = line[i].lp.x; p.y = line[i].lp.y; 13. p.tag = 0; p.line1 = i; p.line2 = -1; 14. insert(heap,m,p); /* 插入左端点,m在insert操作中递增 */ 15. p.x = line[i].rp.x; p.y = line[i].rp.y; 16. p.tag = 1; p.line1 = i; p.line2 = -1; 17. insert(heap,m,p); 18. } 19. while (m>0) { /* 插入右端点,m在insert操作中递增 */ /* m:最小堆元素计数器,k:线段交点计数器 */ 20. p = delete_min(heap,m);/* 取事件调度点,m在delete_min中递减*/ 21. if (p.tag==0) { /* 左端点处理 */ 22. snode = insert_t(p.line1,s,p.x); 23. line_1 = above_t(snode,s); 24. line_2 = below_t(snode,s); 25. process(line_1,p.line1,T,L,k,heap,m,b); 26. process(p.line1,line_2,T,L,k,heap,m,b); 27. } 28. else if (p.tag ==1) { /* 右端点处理 */ 29. snode = search_t(p.line1,s,p.x); 30. line_1 = above_t(snode,s); 31. line_2 = below_t(snode,s); 32. delete_t(snode,s); 33. process(line_1,line_2,T,L,k,heap,m,b); 34. } 9 35. else { /* 交点处理 */ 36. snode = search_t(p.line1,s,p.x); 37. line_1 = above(snode,s); 48. snode1 = search_t(p.line2,s,p,x); 39. line_2 = below(snode1,s); 40. process(line_1,p.line2,T,L,k,heap,m,b); 41. process(line_2,p.line1,T,L,k,heap,m,b); 42. temp = snode->line; snode->line = snode1->line; 43. snode1->line = temp; 44. } 45. } 46. } 1. void process(int line_h,int line_l,POINT T[],LINE_PAIR L[],int &k, HEAP heap[],int &m,BOOL b[][]) 2. { 3. POINT point; 4. HEAP p; 5. if (!(b[line_h][line_l])) { 6. b[line_h][line_l] = TRUE; 7. b[line_l][line_h] = TRUE; 9. p.x = point.x; 10. p.y = point.y; 11 p.tag = 2; 12. p.line1 = line_h; 13. p.line2 = line_l; 14. insert(heap,m,p); 15. T[k] = point; /* 保存交点 */ /* 保存相交的线段编号 */ /* 置交点计算标志 */ /* 把交点插入堆中 */ /* 交点已计算?*/ 8. if (inter_sec(line_h,line_l,point)) { /* 判断并计算交点 */ 16. L[k].line1 = line_h; 17. L[k++].line2 = line_l; 18. } 19. } 20. } 六、复杂性分析 1、数据复杂性: 把n条线段的端点插入最小堆,需O(nlogn)时间。 10 如果交点个数为m,则要处理的事件调度点的数目为2nm。 对堆的每一个insert操作和delete_min操作,需O(log(2nm))。 对二叉树s的每一个操作,需O(logn)时间。 process中的inter_sec操作,需O(1)时间。 因此,每处理一个事件调度点,最多需O(log(2nm))时间。 所以总时间最多为O((2nm)log(2nm))。 2、空间复杂性: 存放事件调度点的空间,最多有2nm个事件调度点,需O(2nm)空间; 存放扫描线状态,最多有n条线段,需O(n)空间; 最后,有一个二维数组b来存放交点的处理标志,需O(n2)空间。 因此,算法所需要的工作单元所需要的空间为O(n2)。 11.3 凸壳问题 定义11.2 令S是平面上的一个点集,封闭S中所有顶点的最小凸多边形,称为S的凸壳,表示为CH(S)。CH(S)上的顶点,有时也叫做S的极点。 凸壳问题是计算几何中最重要的问题之一。它的提法是:给定平面上n个点的集合S,求S的凸壳CH(S)。凸壳问题也可以用几何扫描方法来求取。 11.3.1 凸壳问题的格雷厄姆(Graham)扫描法 一、凸壳问题 1、凸壳的定义 定义11.2 令S是平面上的一个点集,封闭S中所有顶点的最小凸多边形,称为S的凸壳,表示为CH(S)。CH(S)上的顶点,有时也叫做S的极点。 2、凸壳问题 给定平面上n个点的集合S,求S的凸壳CH(S)。 1、在平面点集S中,寻找Y坐标最小的点。把它称为p0。 2、以p0为源点,对所有点的坐标进行变换。 3、对变换后的所有点,以p0为源点,计算它们的极坐标幅角。 4、以幅角的非降顺序来排序S{p0}中的点,令排序过的点为T{p1,p2,,pn1},其中,p1和pn1分别与p0构成最小与最大的幅角。 5、把点集T中的元素,作为事件调度点进行扫描。 6、用堆栈CS作为扫描过程中局部构成的半封闭凸多边形,把它作为扫描线状态维护。7、堆栈初始化为CS{pn1,p0},其中,p0为栈顶元素。 8、按极坐标的幅角,从p1开始,到pn1为止进行扫描。 11 二、思想方法 9、扫描处理: 假定,在某一个时刻,堆栈内容为: CS{pn1,p0,,pi,pj,pk} 其中,pk为栈顶元素,则栈中元素按顺序构成半封闭的凸多边形。 令pl是正在扫描的点,如果由pj、pk、pl所构成的路径是左转的, 则由pj、pk、pl形成的边是凸边,把pkpl作为凸多边形中的边加入进来,把pl 压入栈顶,把扫描线移到下一点; 如果由pj、pk、pl所构成的路径是右转的, 则由pj、pk、pl形成的边是凹边,pk不是凸壳的极点。把pk弹出栈顶,扫描线仍然停留在pl上。 在下一轮处理中,将由pi、pj、pl进行判断和作出处理。 例11.2 求图11.7所示点集的凸壳。 y p8 p6 p9 p10 p7 p4 p3 p11 p5 p1 p2 p12 p0 x 图11.7 以p0为源点、按极坐标幅角排序的点集 y p8 p6 p9 p10 p7 扫描线 p4 p3 p11 p5 p1 p2 p12 p0 x 图11.8 处理p5之后产生的凸壳 12 y p8 p6 p9 p10 p7 p4 p3 p11 p5 p1 p2 p12 p0 x 图11.9 处理p6之后产生的凸壳 y p8 p6 p9 p10 p7 p4 p3 p11 p5 p2 p1 p12 p0 x 图11.10 最后得到的凸壳 11.3.2 格雷厄姆扫描法的实现 一、算法步骤: 1. 求平面点集S中Y坐标最小的点p0; 2. 以p0为源点,变换S{p0}中所有点的坐标; 3. 以p0为源点,计算S{p0}中所有点的幅角; 4. 以幅角的非降顺序排序S{p0}中所有的点,令事件调度点T{p1,p2,,pn1}是排序过的数组; 5. 初始化堆栈:令CS[0]pn1,CS[1]p0;令堆栈指针sp1,事件调度点数组T的下标k0; 6. 如果kn1,转7;否则,算法结束; 7. 按(11.1.7)式计算CS[sp1],CS[sp],T[k]所构成的三角区符号D,若D0, spsp1,CS[sp]T[k],kk1;转6;否则,spsp1;转6; 二、数据结构: typedef struct { float x; /* X坐标 */ 13 float float } SPOINT; POINT SPOINT POINT y; ang; S[n]; T[n]; /* Y坐标 */ /* 极坐标的幅角 */ /* 平面点集 */ /* 按幅角的非降顺序排序的平面点集 */ /* 构成凸壳的点集 */ CS[n]; 三。算法的实现: 算法11.2 求平面点集的凸壳 输入:平面点集S[],顶点个数n 输出:构成凸壳的极点CS[];极点个数sp 1. void convex_hull(POINT S[],POINT CS[],int n,int &sp) 2. { 3. int i,k; 4. float D; 5. SPOINT T[n]; 6. for (i=1;i 7. if ((S[i].y /* 求T[i]的幅角 */ 10. T[i-1].x = S[i].x – S[0].x; T[i].y = S[i].y – S[0].y; 11. T[i-1].ang = atan(T[i-1].y,T[i-1].x); 12. } 13. sort(T,n-1); /* 按T[i]幅角的非降顺序排序T[i] */ 14. CS[0].x = T[n-2].x; CS[0].y = T[n-2].y; 15. CS[1].x = 0; CS[1].y = 0; sp = 1; k = 0; 16. while (k 17. D = CS[sp-1].x*CS[sp].y + CS[sp].x*T[k].y + T[k].x*CS[sp-1].y 18. - CS[sp-1].y*CS[sp].x - CS[sp].y*T[k].x - T[k].y*CS[sp-1].x; 19. if (D>=0) 20. CS[++sp] = T[k++]; /* 若D0,T[k]压入栈顶,扫描线前移一点 */ 21. else sp--; 22. } 23. } /* 否则,弹出栈顶元素 */ 四、复杂性分析 1、数据复杂性: 14 第6~8行及9~12行,各需(n)时间; 第13行的排序操作,需O(nlogn)时间; 第16~22行的while循环,需(n)时间。 因此,算法的时间复杂性是O(nlogn)。 2、空间复杂性: 用于输入的平面点集、用于输出的凸壳极点需要(n)空间 事件调度点数组,需要(n)空间。 11.4 平面点集的直径问题 11.4.1 求取平面点集直径的思想方法 一、基本概念 1、平面点集的直径 令S是平面上的点集,S的直径定义为S中两点之间的最大距离,记为Diam(S) 2、平面点集的直径与凸壳的关系 凸壳是封闭S中所有顶点的最小凸多边形,点集S的直径等于它的凸壳上顶点的直径,即Diam(S)Diam(CH(S)),也即凸壳上任何两点之间的最大距离。 3、凸多边形的支撑线 定义11.3 P是凸多边形,P的支撑线是通过P的一个顶点的直线l,使得P完全位于l的一侧。 例:图11.11表示了凸多边形的一些支撑线。 l1 p2 l2 l3 p1 p3 l4 p6 p4 p5 l6 l5 图11.11 凸多边形的支撑线 4、凸多边形支撑线与凸多边形直径的关系: 定理11.1 凸多边形P的直径等于P的任何一对平行支撑线之间的最大距离。 例:图11.11中,l1与l6是任何一对平行支撑线中,距离最大的一对。它就是顶点对p1与p6之间的距离,也是凸多边形P的距离。 5、对跖对(antipodal pair) 定义11.4 通过两条平行支撑线的凸多边形P上的任何两顶点,叫做对跖对 例:在图11.11中,对应于平行支撑线l1与l6的对跖对是(p1,p6), 对应于平行支撑线l2与l5的对跖对是(p2,p5)。 15 二、求平面点集直径的思想方法 1、思想方法 把计算平面点集凸壳的直径,转化为在凸壳上寻找所有的对跖对,然后,选择最大距离的对跖对。 2、顶点与边之间距离的确定 线段qr是凸多边形上的一条边, 点p是凸多边形上的一个顶点。 延伸线段qr的两个端点,使之成为直线l, 点p到线段qr的距离,定义为点p到直线l的距离,记为dist(q,r,p)。 假定,q,r,p三点的坐标分别为:(x1,y1),(x2,y2),(x0,y0), 线段qr的直线方程为 AxByC0 点p到线段qr的距离,可由下式确定: dist(q,r,p)|Ax0By0C|AB22 (11.1.8) 3、对跖对的求取 对某个mn,令点集p1,p2,,pm是凸壳上反时针顺序的所有顶点。 在以反时针顺序遍历凸壳上的顶点时,令pk是第一个距离线段pmp1最远的顶点, pl是第一个距离线段p1p2最远的顶点,ps是第一个距离线段p2p3最远的顶点。 pk和pl之间(包括pk和pl)的所有顶点,都可以与p1构成对跖对。 pl和ps之间的顶点,lsm,都可以与p2构成对跖对 p 7 p 6 p 8 p 5 p 9 p 4 p 10 p 11 p 3 p 2 p 12 p 1 图11.12 寻找对跖对 4、平面点集直径的求取: 1)从p2开始,以反时针顺序遍历凸壳的顶点,寻找距离pmp1最远的顶点pk, 2)把顶点对(p1,pk)放入初始化为空的对跖对集合A中。 3)从pk开始,以反时针顺序遍历凸壳的顶点,寻找与p1p2的距离递增的顶点pj,一直到与p1p2的距离最远的顶点pl为止。 16 4)把所有这些顶点对(p1,pj)及(p1,pl)放入A中。 5)继续从pl开始,以反时针顺序遍历凸壳的顶点,寻找与p2p3的距离递增的顶点pt,一直到与p2p3的距离最远的顶点ps为止。并把(p2,pl)到(p2,ps)的所有顶点对放入A中。 6)这个过程一直继续,直到反时针遍历完凸壳的所有顶点,或者遇到线段pkpk1为止。 7)扫描A中的所有对跖对,找出距离最大的一对对跖对,它的距离就是凸壳的直径。 11.4.2 平面点集直径的求取 一、求平面点集直径问题的步骤: 1. 求点集S的凸壳CH(S){p1,p2,,pm}; 2. 令A{},k2; 3. 寻找pk:如果dist(pm,p1,pk1)dist(pm,p1,pk),则kk1,并继续第3步; 否则,转4; 4. 令i1,jk; 5. 如果ik并且jm,则AA{(pi,pj)},转6;否则,转8; 6. 如果dist(pi,pi1,pj1)dist(pi,pi1,pj),并且jm,转7;否则,ii1,转5; 7. AA{(pi,pj1)},jj1,转6; 8. 求取A中距离最远的对跖对(pr,ps),则(pr,ps)的距离就是平面点集S的直径。 二、数据结构: typedef struct { POINT POINT } ANTIPODAL; ANTIPODAL POINT POINT A[n]; S[n]; /* 对跖对集合 */ /* 平面点集 */ /* 构成凸壳的点集 */ p1; p2; /* 对跖对数据结构 */ CS[n]; 三、算法的实现: 算法11.3 求平面点集S的直径 输入:平面点集S[],顶点个数n 输出:距离最远的对跖对a及其距离d,即点集S的直径 1. void diameter(POINT S[],int n,ANTIPODAL &a,float &d) 2. { 3. int i,j,k,m,s; 17 4. float x,y; 4. POINT CS[n]; 5. ANTIPODAL A[n]; 6. convex_hull(S[],CS[],n,&m); 7. k = 2; 8. while (dist(CS[m],CS[1],CS[k+1])>(dist(CS[m],CS[1],CS[k])) 9. k = k + 1; /* 寻找距离线段CS[m],CS[1]最远的顶点CS[k] */ /* i从1扫描到k,j从k扫描到m*/ 10. i = 1; j = k; s = 0; 11. while ((i<=k)&&(j<=m)) { 12. A[s].p1 = CS[i]; A[s++].p2 = CS[j]; 13. while ((dist(CS[i],CS[i+1],CS[j+1])>= (dist(CS[i],CS[i+1],CS[j])) && (j 18. x = A[0].p1.x – A[0].p2.x; y = A[0].p1.y – A[0].p2.y; 19. d = x * x + y * y; i = 0; 20. for (j=1;j 21. x = A[j].p1.x – A[j].p2.x; y = A[j].p1.y – A[j].p2.y; 22. x = x * x + y * y; 23. if (d 27. a = CS[i]; d = sqrt(d); 28. } /* 寻找对跖对CS[i],CS[j] */ 四、复杂性分析 1、时间复杂性 第6行调用convex_hull函数计算点集S的凸壳,需O(nlogn)时间。 第8~17行寻找构成对跖对的CS[i]和CS[j], 如果凸壳的顶点个数为m,没有包含平行边, 则判断两个顶点是否构成对跖对的次数恰好为m次。 如果包含平行边,平行边的数目不会超过m/2, 因此,判断的总次数不会超过3m/2O(n)。 所以,寻找对跖对的时间为O(n)。 对跖对的总个数也不会超过上述数目, 18 所以,第18行开始的寻找距离最远的对跖对,所需时间也是O(n)。 因此,算法的运行时间是O(nlogn)时间。 2、空间复杂性 存放输入点集所需要的空间为(n), 需要存放的点集S的凸壳的顶点,不会超过n个, 需要存放的对跖对不会超过n对, 因此,用于存放凸壳的顶点及对跖对的工作空间为O(n)。 19 因篇幅问题不能全部显示,请点此查看更多更全内容/* 以S[0] 为源点,变换S[i]的坐标于T[i] *//* 寻找距离最远的对跖对 */
Copyright © 2019- 69lv.com 版权所有 湘ICP备2023021910号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务