/ p2 o! F' [2 z' X% j. O! r& Q确定一点在A中、另一点在B中的最近点对9 H4 V/ I; C# M5 T6 w
3 k, I# [# A' L4 ]( M( n% ^* [
从上面得到的三对点中,找出距离最小的一对点8 ^, g& q% w, ?. ?6 X
& n" C. ]. g& ^ _图14-13 寻找最近的点对 4 w" Q d# Q7 e. D- C7 s' V$ ^' j2 ^! s/ a, D
0 s+ S" S- l- ]
为了确定第三种情况下的最近点对,需要采用一种不同的方法。这种方法取决于点集是如何被划分成A、B的。一个合理的划分方法是从xi(中间值)处划一条垂线,线左边的点属于A,线右边的点属于B。位于垂线上的点可在A和B之间分配,以便满足A、B的大小。/ ^ \" U/ I! w, p( `8 t* u/ |
+ C# l" {. M, \5 D. B" x+ B/ w1 p
例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。 % I% g- {$ u# D* Z+ r; K6 ~! d3 |- F8 i" @0 a! K* t. s, g* I/ x( R
设是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)。3 ?* ~" b+ G, e
5 ^- T, ~- R. T$ B+ ?& P例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) 是最近的点对。6 E# s- x& K' A) w
1 [% E- s- L; }& `) G
为了确定一个距离更小的第三类点,RA 中的每个点最多只需和RB 中的6个点比较,如图1 4 - 1 6所示。 ]) q$ T- s J
) `1 u* O( i6 j$ X3 r1. 选择数据结构5 ~7 u: [! b! d( s
- l7 L- g2 B) O# ?7 a( I, I9 `为了实现图1 4 - 1 3的分而治之算法,需要确定什么是“小问题”以及如何表示点。由于集合中少于两点时不存在最近点对,因此必须保证分解过程不会产生少于两点的点集。如果将少于四点的点集做为“小问题”,就可以避免产生少于两点的点集。( Z. z" r+ U# H$ g) [
6 c- I& f6 Z1 R
每个点可有三个参数:标号, x 坐标,y 坐标。假设标号为整数,每个点可用P o i n t l类(见程序1 4 - 8)来表示。为了便于按x 坐标对各个点排序,可重载操作符<=。归并排序程序如1 4 -3所示。 - s0 |: o. R6 X t. w$ a/ P) ?9 `8 [/ B* H# K& [! P' h
程序14-8 点类 @: i6 j5 ]) O+ [$ L7 | - q, b2 Q" f. L* Z0 P1 Qclass Point1 {, ^8 i1 K' A% g
; T9 D- c- |8 M/ H: R
friend float dist(const Point1&, const Point1&);+ A+ ]/ _8 d4 K* V; ~0 a- @
K$ v! @ l) w [, V9 [friend void close(Point1 *, Point2 *, Point2 *, int, int, Point1&, Point1&, float&); 4 a- ~) D3 k W$ E6 E' T6 P* @1 d, K. }( L
friend bool closest(Point1 *, int, Point1&, Point1&,float&); 9 |( o9 T3 {6 n* Z* E- ~2 z f+ z* S) Y. @
friend void main();4 c# h, P8 |9 N3 F/ T( A; g3 p
# G6 c8 S F1 R+ M1 ~8 A2 r
p u b l i c : 9 i9 i8 U g0 n' s, m$ Q . ?0 T9 o5 p2 k+ E1 Q' e% rint operator<=(Point1 a) const 0 V( j4 K1 c9 V* U6 R . C6 c: A. B _8 i2 Y# L. k{return (x <= a.x);} I3 K* i7 \5 A6 Z+ P! \. |
& o1 h+ [9 Z( y8 {
p r i v a t e :$ E' k' ^/ U+ b- N1 {2 t. W& G- U
" \! l0 B' F) S3 s! b& k' hint ID; // 点的编号 & [0 a; ^8 U2 e ( R- Q/ Z5 I% \" yfloat x, y; // 点坐标0 G# K! x/ ]& ]. K
h4 n. u5 }: K} ; % m) f' d j% Z$ ^' b6 L4 ]. `( I7 t$ W! {' P* s" U; k5 n
class Point2 { 1 C R w; ^6 `$ i( X 3 V- o8 t: H4 F5 ]8 kfriend float dist(const Point2&, const Point2&);* U$ O: d' j y0 c" L
4 g: r$ @7 ]; ?7 m' m" |5 t
friend void close(Point1 *, Point2 *, Point2 *, int, int, Point1&, Point1&, float&); / `, X' f! C2 T) l i, c) q: z, q$ L$ c; R" S4 L3 X" x
friend bool closest(Point1 *, int, Point1&, Point1&, float&); . Z2 O+ T! {) o1 M6 y) R* E - d2 G: V+ y; H9 c3 Y" Q+ g" `, rfriend void main();9 Y& S2 y& ~! Y) H; |5 p* N
9 g7 k# Z% C4 k* F3 l4 dp u b l i c : " N* b# i6 I% ~' `( ?7 {. E0 a4 a8 i. X0 | F% v6 e: D
int operator<=(Point2 a) const . }0 O! m# d' J0 `" |2 {4 Z$ k- r" I6 n
{return (y <= a.y);} * ?* L6 Q4 r, L! |4 I1 R 3 s* k9 V# \5 Wp r i v a t e : : X2 R( n7 Q0 m5 C: L8 O, [# ^
int p; // 数组X中相同点的索引- C) c& P5 f z' r1 E" I
/ w5 l& n+ x" D! F' u
float x, y; // 点坐标 3 X9 m% E* [: h( m- [) ~/ d; m; X7 x0 l; e/ {5 @- Y3 L5 C& ^
} ; 9 A$ ~) C# N k* B4 b* ^ + [2 t/ R& v# q# Y) _- I9 L2 s所输入的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中的对应点。- b5 c9 p9 y( ?/ F3 b% z3 g
0 R/ n' s% l0 X, Z6 G
确定了必要的数据结构之后,再来看看所要产生的代码。首先定义一个模板函数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类的友元。 . q" J% g7 B! o4 m8 n& \/ S ( R& e4 z; {( O; O+ [! [8 R程序14-9 计算两点距离- X' X3 ?5 C9 ^9 b
7 o3 C* F& M/ P E/ o$ w; tfloat dx = u.x-v. x ;1 l6 N+ B4 \! N
6 ?% A3 \) q2 |+ Yfloat dy = u.y-v. y ;2 ?9 o- d7 ?% S2 v7 M
& F ?3 |5 E2 j- F
return sqrt(dx * dx + dy * dy); 6 m7 G1 E E+ T7 V; y5 P/ X7 t7 [/ _: r3 T3 M
} 2 [9 R5 _' d" g2 z( D# [" s6 x8 M' i/ n( B7 D4 I
如果点的数目少于两个,则函数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 ),该函数实际求解最近点对。 " N/ s$ z7 d f: U/ C% U3 k3 _. j, p/ \: Z2 [
程序14-10 预处理及调用c l o s e I$ k2 H2 A4 q- o9 ^
4 n8 L3 b/ Q. Rbool closest(Point1 X[], int n, Point1& a, Point1& b, float& d)+ ~' T4 k+ D# e9 l
0 ]# b* u5 I& E$ w
{// 在n >= 2 个点中寻找最近点对 & g) |# Y( w+ r; R * [1 d. O: o5 ~& K1 H0 o" g! }// 如果少于2个点,则返回f a l s e& Y( A5 F* [" ~* @9 u1 F
* l0 U+ Q7 F2 W4 @; X. qM e r g e S o r t ( X , n ) ; * P8 S: e0 N3 ?( x( s) E) _" }( e" _$ L5 X2 N. @
// 创建一个按y坐标排序的点数组. W% b& F, w L3 n- c) ^) h
5 W [8 p6 \9 d# A# d$ e" W
Point2 *Y = new Point2 [n]; ( \: O3 R: n$ L% E % F7 u" c8 k6 d0 N/ B; xfor (int i = 0; i < n; i++) {# \1 O# k' k4 z; W, C: U/ N+ r" Z
1 ?$ R0 s$ u( B9 A" H+ [- Y1 | x// 将点i 从X 复制到Y % X1 U+ K) q# `6 n3 {4 Q h! L5 s4 v& @: XY.p = i; # A+ I2 ~& q/ O3 Y9 K0 a; A 4 s9 { m7 R' r5 }3 K% p KY.x = X.x;0 ?( n) W$ b$ s1 o, @
! e& [6 p! @/ J% g& z/ [- ?
Y.y = X.y; " \4 y# @5 |/ p/ r' r4 v" _ ' [9 N0 f% V3 M( A4 w3 T} 1 G7 Y: B% j( ]. f+ H! H1 b/ x. I3 f( z2 }8 J
M e r g e S o r t ( Y,n); // 按y坐标排序 . c9 k( a0 z- H$ }: @3 A. D- \* a7 X2 h* t" |
// 创建临时数组 ^: B8 g- h: t3 r. _
+ C+ q: ~+ X5 b4 f; n
Point2 *Z = new Point2 [n];7 q3 k8 Y, m# @% K( p
& @' d# k! k& p/ h( r// 寻找最近点对 _& g9 j, v- k
# t- e+ j' X% Q u5 m3 G
c l o s e ( X , Y, Z , 0 , n - 1 , a , b , d ) ; T/ X: J% p& W( a" Y+ M7 G& A& W8 K1 u* w) S' D. U$ j" n4 y- E8 N
// 删除数组并返回1 C$ q1 g- P* P4 s
: D4 @9 z3 x+ i) d% p5 R/ K1 `6 odelete [] Y;$ z% t% y c6 h+ x u3 I
: f- C% D, G% c' A! ]. @7 e: J5 w
delete [] Z; ! [4 @) e& R @* o. A : h( m( z( k2 C5 Treturn true;' D8 @. q1 s+ G* {7 p
* P1 c# X$ ^1 S* x& c1 {} 8 v/ z {' P) z8 L9 ~3 Y. @: A+ U) p& D; h" Y+ {
程序1 4 - 11 计算最近点对 * v X6 x! M: H3 p/ G' q& y6 a0 Q. N. A& _, e8 |6 K. }6 `. ^
void close(Point1 X[], Point2 Y[], Point2 Z[], int l, int r, Point1& a, Point1& b, float& d)1 b( K' K* E. o+ g
# {2 @) `3 p, t# ~{//X[l:r] 按x坐标排序 . V8 ]0 j2 \. S t! y% j' e0 i; `1 B2 c$ Y
//Y[l:r] 按y坐标排序. ]! g4 ^' ~1 H p5 A
: D5 B' ~: k% `6 t2 V% \) Gb = X[r]; : b7 n- N' F8 d3 k7 N / z+ }+ f: v: _2 Id = d3;} % N' s& M, i; U4 l 2 l# Y8 h" M, ~# m# er e t u r n ; }+ \* x8 f) N) h: T4 E8 D
; z, q$ Q a s, D/ /多于三个点,划分为两部分) e& e4 _3 {' d
$ A, b" B& F& R) I* ~' gint m = (l+r)/2; // X[l:m] 在A中,余下的在B中 ( v; R" T$ O, i$ G! @! u. ^0 h1 Q1 L4 }* J* u& C) r7 G- F
// 在Z[l:m] 和Z [ m + 1 : r ]中创建按y排序的表" ~8 ]" E& k4 a( S- j
; I! g. _( Y1 b: V# ]% s8 `
int f = l, // Z[l:m]的游标 % _. r: g* |' [6 [# H6 T4 O) } ( z8 g6 [, O; V2 [+ f- ^9 i* z% Mg = m+1; // Z[m+1:r]的游标& N, I" a- K6 J
1 s1 o& f, C y5 V2 j. d
for (int i = l; i <= r; i++): g! ?6 n- T6 b6 s
1 w7 @3 t$ k+ Z/ D; \5 g. Rif (Y.p > m) Z[g++] = Y; 7 Y" Y. `2 ]! z+ D9 {- x \( n6 f1 a: {7 k& J3 t, helse Z[f++] = Y;+ L5 i' q/ E0 s5 e* S. \) y
- V, |9 n5 \6 U4 _3 Z- _0 U' i// 对以上两个部分进行求解 1 o* a$ Z9 u) }* w0 A6 Z3 P9 S, L4 m" d& M4 t
c l o s e ( X , Z , Y, l , m , a , b , d ) ;. f; h# R& W( I; {* ^/ w9 l- V7 V
5 s, V6 v5 G; _8 _" A
float dr;8 v5 C- f7 Y' Y) U
! C- B2 I) x, o7 A6 J% y
Point1 ar, br;/ |6 z n( G* R# |+ R/ D
* t+ K1 Y. r1 [: sc l o s e ( X , Z , Y, m + 1 , r, a r, b r, d r ) ;! z) R9 F3 ]# }2 a
4 g2 X8 Y* B9 T0 x// (a,b) 是两者中较近的点对, O% ~* X; B/ B# f# U
; R4 _) r. S* X
if (dr < d) {a = ar; * b4 P# R |7 p9 M' o! m& e) X. I: B' @! C6 J
b = br;/ {: }5 u) c8 S, O
) s/ W' g! r+ q0 U
d = dr;}5 D' d( G2 ^; Q
4 @, a, {& i# L/ {8 A0 @* uM e r g e ( Z , Y,l,m,r);// 重构Y , m- R! n( j- w! z+ T8 h; G 6 K C+ m' _0 j4 s/ /距离小于d的点放入Z: C& F' Z$ ]$ }" Y, ]3 Q
" p2 E, y# g2 h2 p* U
int k = l; // Z的游标1 {& o! U5 k8 a) B# V+ p9 W Q9 d
4 @7 ~4 O! r8 e
for (i = l; i <= r; i++) . o6 N Z- I! E" W. C& R2 I; b! P+ y( D
if (fabs(Y[m].x - Y.x) < d) Z[k++] = Y; % h( x1 l' D. q2 }, B6 @3 O& ]% R# J4 R
// 通过检查Z [ l : k - 1 ]中的所有点对,寻找较近的点对& d+ A% V9 K ]- h. L
+ q. X* P. q5 a+ K# f
for (i = l; i < k; i++){/ ?, I1 C. s0 H& d
5 b" S: I) e b
for (int j = i+1; j < k && Z[j].y - Z.y < d; % u9 I% N% {5 x/ H1 ? & h1 N I8 g, E2 J% X( L: d: \! ej + + ) { 4 G) Y. n& x& h" u: z2 A0 @2 j5 O6 V# k, }/ Z0 U% r2 Z- X1 U, u b
float dp = dist(Z, Z[j]);8 d$ r4 ~+ F% \+ @4 B& d
, k4 a" Y- l' u/ A( L
if (dp < d) {// 较近的点对 ! U0 \2 q+ [& e% F + C. V$ q/ Y: F8 B2 W7 X" V7 pd = dp;3 s& l- ?/ `) I J
! I3 D3 l: I% w5 }: B
a = X[Z.p]; / t& a0 h$ g+ z- d" [+ S + Z, d) |& e- ?* d) Tb = X[Z[j].p];}( Q1 b3 Z: E$ j* M1 ~; x6 m) ]" D# g
& n) t; r7 I9 R8 t) y9 x}8 w2 E6 m' @% e B! J
* y. Y2 H+ s6 R d( k) m: v} / F4 Y. ~0 i* |- D3 X5 R" G 8 b* e( f* `9 A" D5 n, w: g6 \+ u3 y/ x} ! d% ?1 S' D8 }1 S8 @( _: t, A5 d) ?9 | 8 ]( G5 U% f. h函数c l o s e(见程序1 4 - 11)用来确定X[1:r] 中的最近点对。假定这些点按x 坐标排序。在Y [ 1 : r ]中对这些点按y 坐标排序。Z[ 1 : r ]用来存放中间结果。找到最近点对以后,将在a, b中返回最近点对,在d 中返回距离,数组Y被恢复为输入状态。函数并未修改数组X。 ) Y! N1 o( z2 D9 @8 V 6 {' y$ c# i. O首先考察“小问题”,即少于四个点的点集。因为分割过程不会产生少于两点的数组,因此只需要处理两点和三点的情形。对于这两种情形,可以尝试所有的可能性。当点数超过三个时,通过计算m = ( 1 + r ) / 2把点集分为两组A和B,X [ 1 : m ]属于A,X [ m + 1 : r ]属于B。通过从左至右扫描Y中的点以及确定哪些点属于A,哪些点属于B,可以创建分别与A组和B组对应的,按y 坐标排序的Z [ 1 : m ]和Z [ m + 1 : r ]。此时Y和Z的角色互相交换,依次执行两个递归调用来获取A和B中的最近点对。在两次递归调用返回后,必须保证Z不发生改变,但对Y则无此要求。不过,仅Y [ l : r ]可能会发生改变。通过合并操作(见程序1 4 - 5)可以以Z [ 1 : r ]重构Y [ 1 : r ]。+ |$ A% v6 f7 n
' l: y8 {6 |. e$ ?- d7 R
为实现图1 4 - 1 6的策略,首先扫描Y [ 1 : r ],并收集距分割线小于的点,将这些点存放在Z [ 1 : k - 1 ]中。可按如下两种方式来把RA中点p 与p 的比较区内的所有点进行配对:1) 与RB 中y 坐标≥p.y 的点配对;2) 与y 坐标≤p.y 的点配对。这可以通过将每个点Z [ i ](1≤i < k,不管该点是在RA 0 n% m4 ^' x0 t: F) O4 l( u; h 8 s" [( T* I# Y# M/ r还是在RB中)与Z[j] 配对来实现,其中i<j 且Z [ j ] . y - Z [ i ] . y< 。对每一个Z [ i ],在2 × 区域内所检查的点如图1 4 - 1 7所示。由于在每个2 × 子区域内的点至少相距。因此每一个子区域中的点数不会超过四个,所以与Z [ i ]配对的点Z [ j ]最多有七个。" h7 f& x0 T. e6 H/ I% A
( c& O2 Y5 t, v+ X; V- K; E这个递归式与归并排序的递归式完全一样,其结果为t (n) = (nl o gn)。另外,函数c l o s e s t还需耗时(nl o gn)来完成如下额外工作:对X进行排序,创建Y和Z,对Y进行排序。因此分而治之最近点对求解算法的时间复杂性为(nl o gn)。</P>