<>给定n 个点(xi,yi)(1≤i≤n),要求找出其中距离最近的两个点。. C' c* y, u: p" \/ V1 q& |
9 {4 c. `1 l- g Y6 ~
例14-7 假设在一片金属上钻n 个大小一样的洞,如果洞太近,金属可能会断。若知道任意两个洞的最小距离,可估计金属断裂的概率。这种最小距离问题实际上也就是距离最近的点对问题。- F9 W1 ^' c! B) M
, j3 h: i4 l! w通过检查所有的n(n- 1 ) / 2对点,并计算每一对点的距离,可以找出距离最近的一对点。这种方法所需要的时间为(n2 )。我们称这种方法为直接方法。图1 4 - 1 3中给出了分而治之求解算法的伪代码。该算法对于小的问题采用直接方法求解,而对于大的问题则首先把它划分为两个较小的问题,其中一个问题(称为A)的大小为「n /2ù,另一个问题(称为B)的大小为「n /2ù。初始时,最近的点对可能属于如下三种情形之一: 1) 两点都在A中(即最近的点对落在A中);2) 两点都在B中;3) 一点在A,一点在B。假定根据这三种情况来确定最近点对,则最近点对是所有三种情况中距离最小的一对点。在第一种情况下可对A进行递归求解,而在第二种情况下可对B进行递归求解。 ; _. f' R3 |% T# U9 k7 v$ \ 4 {$ ?: L/ b; H/ o! x( B- F" r0 ?- n: s# O7 U F3 V) G8 ~8 q
if (n较小) {用直接法寻找最近点对9 ?+ t9 |3 C( ]
- D$ U' c8 @: R0 @) O
R e t u r n ; } . p1 p# w& [* R; n- ~0 t; R & N7 ^- K" }# e" s; s1 Z; m// n较大4 ^5 {4 Y; J L
X a: P* s+ n# o" ?2 h/ g图14-13 寻找最近的点对 ! T6 {5 v# d9 p: f: t5 v b4 A/ p' v3 g) X6 k' u 9 x4 r9 C, x# B) E3 z f9 d# F为了确定第三种情况下的最近点对,需要采用一种不同的方法。这种方法取决于点集是如何被划分成A、B的。一个合理的划分方法是从xi(中间值)处划一条垂线,线左边的点属于A,线右边的点属于B。位于垂线上的点可在A和B之间分配,以便满足A、B的大小。 ( ?' y# R: O% f/ e! _! [$ N6 ?# z: ]' Y1 _2 @! P) W
例2-8 考察图14-14a 中从a到n的1 4个点。这些点标绘在图14-14b 中。中点xi = 1,垂线x = 1如图14-14b 中的虚线所示。虚线左边的点(如b, c, h, n, i)属于A,右边的点(如a, e, f, j, k, l) 属于B。d, g, m 落在垂线上,可将其中两个加入A, 另一个加入B,以便A、B中包含相同的点数。假设d ,m加入A,g加入B。 3 X& u# }4 N, Z, c U! i( J1 Q7 X9 O8 c , ~2 @' f! X; k% n( w设是i 的最近点对和B的最近点对中距离较小的一对点。若第三种情况下的最近点对比小。则每一个点距垂线的距离必小于,这样,就可以淘汰那些距垂线距离≥ 的点。图1 4 - 1 5中的虚线是分割线。阴影部分以分割线为中线,宽为2 。边界线及其以外的点均被淘汰掉,只有阴影中的点被保留下来,以便确定是否存在第三类点对(对应于第三种情况)其距离小于。用RA、RB 分别表示A和B中剩下的点。如果存在点对(p,q),p?A, q?B且p, q 的距离小于,则p?RA,q?RB。可以通过每次检查RA 中一个点来寻找这样的点对。假设考察RA 中的p 点,p的y 坐标为p.y,那么只需检查RB 中满足p.y- <q.y<p.y+ 的q 点,看是否存在与p 间距小于的点。在图14-16a 中给出了包含这种q 点的RB 的范围。因此,只需将RB 中位于×2 阴影内的点逐个与p 配对,以判断p 是否是距离小于的第三类点。这个×2 区域被称为是p 的比较区(comparing region)。 * q5 R! @' _8 p4 k+ x6 R0 }7 \& `" S
例2-9 考察例2 - 8中的1 4个点。A中的最近点对为(b,h),其距离约为0 . 3 1 6。B中最近点对为(f, j),其距离为0 . 3,因此= 0 . 3。当考察是否存在第三类点时,除d, g, i, l, m 以外的点均被淘汰,因为它们距分割线x= 1的距离≥ 。RA ={d, i, m},RB= {g, l},由于d 和m 的比较区中没有点,只需考察i即可。i 的比较区中仅含点l。计算i 和l的距离,发现它小于,因此(i, l) 是最近的点对。7 J3 N9 G! k& `
9 y& G/ F: M& j1 Q
为了确定一个距离更小的第三类点,RA 中的每个点最多只需和RB 中的6个点比较,如图1 4 - 1 6所示。 , O }4 W6 }( V0 O4 m% c0 J4 k. ?% \" A. s" R4 c: P3 m' X
1. 选择数据结构 # C8 S+ p5 H% v! [3 U4 m8 `& s* H! o0 }* \* _+ u8 ^. F% m* T
为了实现图1 4 - 1 3的分而治之算法,需要确定什么是“小问题”以及如何表示点。由于集合中少于两点时不存在最近点对,因此必须保证分解过程不会产生少于两点的点集。如果将少于四点的点集做为“小问题”,就可以避免产生少于两点的点集。) ^. \- w( E" ?6 y$ y" K# b
' S9 _/ W* T& p* @" d
每个点可有三个参数:标号, x 坐标,y 坐标。假设标号为整数,每个点可用P o i n t l类(见程序1 4 - 8)来表示。为了便于按x 坐标对各个点排序,可重载操作符<=。归并排序程序如1 4 -3所示。 ' W8 c% k# o) A/ a* i " d5 ~" W) ]/ ~程序14-8 点类* b" l: J+ W- F @# j3 W7 G
* P. V" A& j/ m2 H/ [3 o3 P2 Rclass Point1 {. \6 N1 b4 W5 x8 A1 e) F
! x0 @* T$ b2 v
friend float dist(const Point1&, const Point1&);1 t2 Q; d- V4 e. @0 b
' D; [- c% d, Z( O4 ^# vfriend void close(Point1 *, Point2 *, Point2 *, int, int, Point1&, Point1&, float&); ]+ s) b: @, V2 K
7 i$ w2 i+ J, k$ k
friend bool closest(Point1 *, int, Point1&, Point1&,float&); $ b, }/ o7 E0 e% Y" n, H. k# B# j* Y7 ^
friend void main(); 7 u4 w+ h7 Q5 R' C6 `$ {- u4 {" s9 U+ _1 p3 c/ C/ @# r9 V
p u b l i c : Z0 p0 X) L- d$ l# s& L& s$ @6 t# F$ _: D9 R6 `5 o# T) p& Y1 t: T
int operator<=(Point1 a) const r- R# f7 ~) B) x$ p4 d) H ! A+ X6 m* @4 E{return (x <= a.x);} @; I2 W. e& K j% \ L8 f) a( ?$ r" }p r i v a t e : $ ]& N Q, j1 y$ g( G7 A# a/ U, m" k3 q: ?0 {' K3 P! z" J5 Z
int ID; // 点的编号 ; A% c9 P6 O# u! m/ N9 u2 i$ V3 A v1 y1 x
float x, y; // 点坐标 % r% w$ m" J; N6 ]4 ^( K% d) ^ - R9 Q4 c: C: C% u0 q9 M7 h8 m1 Y} ; " }. F% o8 A) h, Z0 U . B9 g: V; p- @# ]0 Hclass Point2 { / `7 L/ `0 P, q0 U) X. b6 f' H
friend float dist(const Point2&, const Point2&); % p3 |7 ^" D8 F* y' w/ U " c% P. P4 s. L1 b2 G2 f+ Ifriend void close(Point1 *, Point2 *, Point2 *, int, int, Point1&, Point1&, float&); ; X0 \, M. h. T/ X H ; V& C2 J% f6 i, D4 r, z' T; d9 Mfriend bool closest(Point1 *, int, Point1&, Point1&, float&);& w2 f: U+ o; E. }
% X: J g) x" B, s$ b8 z, n
friend void main();: l& v% v& P: [1 y" }3 V/ b" q/ J& {
: O- _# X I" b" r" D, c: J. R& s
p u b l i c : / f% ~$ m* |7 D9 x" x' a8 K* N8 D+ {! d% Z9 a+ b4 O4 N- K
int operator<=(Point2 a) const ' ~, L, U/ u5 a; l4 c' ]: b' u" g" M, b' p6 w
{return (y <= a.y);} + E+ P& u8 A7 W1 C- v5 |/ K7 n; {; f9 N8 ~& ]
p r i v a t e : 7 l5 |- r k$ z$ b3 J* r& N j: k j. X2 W" a
int p; // 数组X中相同点的索引 4 C: t! k3 f, X- u6 u0 q* w * p5 T2 L9 A9 k( O! B5 cfloat x, y; // 点坐标5 Z/ R f. O2 N, Z9 o2 t
5 D5 n I$ s- L* F9 m} ;" Q4 v9 {. d! L) |) o
; q S- c1 j# u2 @* q& h
所输入的n 个点可以用数组X来表示。假设X中的点已按照x 坐标排序,在分割过程中如果当前考察的点是X [l :r],那么首先计算m= (l+r) / 2,X[ l:m]中的点属于A,剩下的点属于B。计算出A和B中的最近点对之后,还需要计算RA 和RB,然后确定是否存在更近的点对,其中一点属于RA,另一点属于RB。如果点已按y 坐标排序,那么可以用一种很简单的方式来测试图1 4 - 1 6。按y 坐标排序的点保存在另一个使用类P o i n t 2 (见程序14-8) 的数组中。注意到在P o i n t 2类中,为了便于y 坐标排序,已重载了操作符<=。成员p 用于指向X中的对应点。5 u" Z% V" |, S. I6 P: [- t* P- d2 h
1 g( G. D( Y" Y! a# ~3 E7 Q
确定了必要的数据结构之后,再来看看所要产生的代码。首先定义一个模板函数d i s t (见程序1 4 - 9 )来计算点a, b 之间的距离。T可能是P o i n t 1或P o i n t 2,因此d i s t必须是P o i n t 1和P o i n t 2类的友元。! e! G/ c( L5 ?! y$ ^8 I% c# f7 F! |
4 |! u" B7 Z: U y( z7 @
程序14-9 计算两点距离8 v$ m4 ?5 k4 \; U) v, }1 e" x# V
1 i$ \; K* A# i0 F9 N# A
template<CLASS T> ( Y3 s7 S4 F! v- a9 H5 N5 _ p% q/ e- y$ ^inline float dist(const T& u, const T& v) : V6 e1 I5 x) I% f$ g- |1 Z1 |7 e& x- k0 d8 U: c
{ / /计算点u 和v之间的距离 . Q% r: e% F2 h & y; c) C4 R$ d; f* k1 r8 Kfloat dx = u.x-v. x ; ! L7 C" V/ V4 c& r2 `8 u/ W, U% V# e; S; X: F! V
float dy = u.y-v. y ; 6 U- m- x5 K( J8 i. f* p 3 V; E' i" q& o+ Ereturn sqrt(dx * dx + dy * dy);. C' Q l3 t( I8 `
4 y) _5 v9 ~+ [, ]
}5 k& J1 t' V, U' {
$ P2 S5 L, O6 V5 c3 o如果点的数目少于两个,则函数c l o s e s t (见程序1 4 - 1 0 )返回f a l s e,如果成功时函数返回t r u e。当函数成功时,在参数a 和b 中返回距离最近的两个点,在参数d 中返回距离。代码首先验证至少存在两点,然后使用M e rg e S o r t函数(见程序14-3) 按x 坐标对X中的点排序。接下来把这些点复制到数组Y中并按y 坐标进行排序。排序完成时,对任一个i,有Y [i ] . y≤Y [i+ 1 ] . y,并且Y [i ] .p给出了点i 在X中的位置。上述准备工作做完以后,调用函数close (见程序1 4 - 11 ),该函数实际求解最近点对。6 ^/ j! c' } V7 Y5 ]2 m, w8 h
% @* ~ y5 s) t
程序14-10 预处理及调用c l o s e; h r) z4 i$ n: ]0 P9 f; K
$ s5 Q U1 A. D9 v8 A; n
bool closest(Point1 X[], int n, Point1& a, Point1& b, float& d) 7 _- P7 t! ^, f2 P1 b+ P. ], `0 r( k. j% E7 {3 ]
{// 在n >= 2 个点中寻找最近点对0 ^" ]; p1 n, r$ P- ?9 L s" ^
0 d7 R3 K) x5 o z$ z0 [- I
// 如果少于2个点,则返回f a l s e8 ^- i: g; E6 {' v* |* u' H3 ?