, W% Y" h) p; ~+ q8 z5 E例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) 是最近的点对。 1 o' Z' h) j1 N2 {) \! f% u 0 n& s$ }1 r! Y6 ^ Q" S为了确定一个距离更小的第三类点,RA 中的每个点最多只需和RB 中的6个点比较,如图1 4 - 1 6所示。 . K6 H4 k2 W6 p, G! g5 T8 ]8 _5 H 3 N# C9 y7 x9 y. e2 c1. 选择数据结构 ( [( Y& v/ y- @( Z( [1 | , q/ o2 G* g( }- H为了实现图1 4 - 1 3的分而治之算法,需要确定什么是“小问题”以及如何表示点。由于集合中少于两点时不存在最近点对,因此必须保证分解过程不会产生少于两点的点集。如果将少于四点的点集做为“小问题”,就可以避免产生少于两点的点集。 $ ^, z1 E' \' e: Z: ~0 g 2 E7 y0 K. U) x3 R每个点可有三个参数:标号, x 坐标,y 坐标。假设标号为整数,每个点可用P o i n t l类(见程序1 4 - 8)来表示。为了便于按x 坐标对各个点排序,可重载操作符<=。归并排序程序如1 4 -3所示。. P9 N+ |" s+ K" N
, U, d" ^ I' `4 ~7 k程序14-8 点类6 _" b3 E' O3 H( C, c1 g# C0 L
: z2 p! a3 q- g: |7 J( j
class Point1 { 4 q) x* F3 X' ?" Y, J# K9 D" i1 b; T" \+ i0 @$ N6 f/ f( d
friend float dist(const Point1&, const Point1&); ' g. B4 p+ G8 p! b; @( { / u" M3 P6 G: e+ u$ ^6 f Cfriend void close(Point1 *, Point2 *, Point2 *, int, int, Point1&, Point1&, float&);+ h' p" Q3 k' m4 f+ T3 A
5 `* b U/ n3 r& |6 T6 u4 t' o" O% [
friend bool closest(Point1 *, int, Point1&, Point1&,float&);. \! v- G' z- j1 |8 a0 z8 m
) m+ B. Z& x2 F6 l( |9 r' B
friend void main(); m+ O) s' _. K( B" b8 z
- @" i) E4 H. l6 Pp u b l i c :' d% D7 @% I0 z( a% v0 L, m
+ b, _7 l' \" V9 J/ |1 V2 a- p" Xint operator<=(Point1 a) const , K! V9 l3 r7 g- O7 o! v; F [7 i9 \6 V. C A
{return (x <= a.x);}2 B( |, W6 u% r* p* Y3 e* e
& T% Q2 r/ S! X, h @9 [p r i v a t e : & m' N/ t* v+ |; Y V+ n; @4 ]% i8 S6 g( a
int ID; // 点的编号4 K9 b# N, v5 D# P7 r
+ M7 ~; M }5 U5 g6 Z" _: C* [
float x, y; // 点坐标6 {$ n- w3 `8 T0 f
( ?7 x& T1 {" S) c ^6 |} ; 5 j6 M) t( S( \) r, r+ o8 q8 L0 _7 q! f0 b! c9 |+ P( d' P
所输入的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中的对应点。 K; u6 q2 F+ `4 i; \. t- x
) B# g" ?6 U+ ?0 z4 Q$ O/ K
确定了必要的数据结构之后,再来看看所要产生的代码。首先定义一个模板函数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类的友元。 4 d( i6 H. ~4 x) b& L- | N o5 p& V4 F7 a. S( {
程序14-9 计算两点距离 c+ y" Z2 t( q% `4 C( t
& c) u6 y9 J6 L+ Ktemplate<CLASS T>" X, q9 S' [) D: R8 [/ a- O
" L# j: `0 w3 ?
inline float dist(const T& u, const T& v) + Z8 `1 C$ L7 t9 B" V. Z6 a7 l0 a z, i) N
{ / /计算点u 和v之间的距离 , _, y" e' j2 g+ u % d! S3 ^! Y S( Mfloat dx = u.x-v. x ; ! b0 W+ Y, C1 d& Q9 Y2 p & U8 W/ ]6 h- _+ n+ C3 Jfloat dy = u.y-v. y ;5 m, A+ O3 ]. b- a2 Y
; l6 }! W3 D3 L( ~$ @0 k6 Zreturn sqrt(dx * dx + dy * dy);+ h# Y. T; [( z k3 r4 I
9 O4 n6 h i+ |- j9 g. J7 l如果点的数目少于两个,则函数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 ),该函数实际求解最近点对。0 W O; n% A! Q5 k0 j- J
4 W6 }, m3 R% E9 `' A _/ e7 [程序14-10 预处理及调用c l o s e ; p/ P# H% e/ F" U# m 7 n# p/ ^0 ]* Z, D$ G- W, f8 V5 abool closest(Point1 X[], int n, Point1& a, Point1& b, float& d) 2 e) _# v" U( \5 D ! {% Y0 p8 {- r, ~" D4 }+ P& ^8 i{// 在n >= 2 个点中寻找最近点对 / H1 y5 {5 h+ Q9 D! X / C* d% L6 s6 M8 K) t// 如果少于2个点,则返回f a l s e- E/ O1 b; m: k; q
$ N% G' T6 |: d. I4 ^
// 否则,在a 和b中返回距离最近的两个点, [6 \" T! B# S* F/ A6 `0 C, P
. t( B' K9 J& W1 Y
if (n < 2) return false;8 L) p5 T! _+ ^ t0 n
% F ^9 W8 Z [3 s3 b// 按x坐标排序 ! ^( m* @3 p# m& ^" v " \$ S P }0 @, ^& U7 c0 ]+ [8 {M e r g e S o r t ( X , n ) ; ( W6 H5 T* K; @ 1 b* H8 e. b. q" i: x) n, J// 创建一个按y坐标排序的点数组 # c: G$ F4 C: `' o/ W; ` ) V4 D( C. N! p0 y+ }" cPoint2 *Y = new Point2 [n];3 u' A, P d2 b2 L
3 |* T- J8 E% C. [/ s! N
for (int i = 0; i < n; i++) {! c8 M* Q6 W( z. }
: K1 K1 t {0 J) h
// 将点i 从X 复制到Y. i0 {3 B3 D6 Q2 u; J1 Q$ C
5 v! R4 C- L. @$ }
Y.p = i; ' J! c9 F0 c/ e. ^' L) R1 U! g 1 R* D; q8 Q6 p9 B1 [7 WY.x = X.x;2 d. F$ K: F9 T! y+ N! q+ z$ i
, p/ ~9 A5 k9 v4 N; T" g7 o+ ]8 H& ?
Y.y = X.y; . W. @" \4 A4 R: Q7 P" m3 u6 A2 ]1 L
} 8 V* |$ P2 z" Q! Z+ K( y: C; V. B+ [5 n; A* q4 b& L+ b2 y8 I5 }
M e r g e S o r t ( Y,n); // 按y坐标排序 ! f' r4 [, e @; |* Z ! M+ t/ J* \8 t// 创建临时数组4 J* k$ r# ~# B! q9 X
" h# k- R! t& R# S yPoint2 *Z = new Point2 [n]; 6 k) J0 o- }! a; D: A4 ]( U" i4 [% R0 N' x9 h8 f* A# s# X/ f
// 寻找最近点对 8 u3 O: J6 Q) F+ S" v" V8 w7 c( K/ R$ O 2 o; K# w( O6 x' W" _$ rc l o s e ( X , Y, Z , 0 , n - 1 , a , b , d ) ; , X: {' j, {7 V6 x! D 8 L) d7 x2 g$ D1 h// 删除数组并返回0 m g6 v* f2 i) F/ Z" g( r2 G' _
& b+ s. t8 Z" N- W4 }4 Edelete [] Y;3 \! U j# k; |8 z
5 b' o# g$ I, v- o; ^4 qif (d1 <= d2 && d1 <= d3) { 9 D( ?! ^; i1 F( }2 G , h) a% J3 ?- sa = X[l]; , o' u% g, L- Y7 d: G2 y8 M- L. R( k 3 \# i7 B0 z4 _& ?b = X[l+1];6 J, l5 A( T4 a4 o9 J* _; r
, @! m. s5 g& U6 x3 ^7 |" e) T9 J+ Z& s
d = d1;2 u, O5 R g% J0 l+ \8 [
; M% @# y0 O7 j; B" ` r. j
r e t u r n ; } : N. d0 ?: f$ C. f6 ~! ?+ W @& R0 \- Z3 G+ N' [
if (d2 <= d3) {a = X[l+1]; . d y( Z7 J. Q; t8 n" P. W' v8 j9 u3 {6 N- b
b = X[r]; " J; O3 V; \. H, l: |! [& O, J% D% h8 L5 y& h) e, c" ?
d = d2;} 4 e( y! |, c7 u( U( W6 r ; K, k! Z7 t' \9 Y; S' M- \' Z2 Xelse {a = X[l]; ' J6 J D1 ]% u7 {/ L ' b4 J- T7 K! Y l# q! M1 {b = X[r];6 u4 X& O7 q- |
+ X/ y6 [2 l& T0 `9 ~0 ]. V8 B
d = d3;} 7 q; k1 q4 h7 o ( ^' j. S) d+ l- v8 d- xr e t u r n ; }4 j( h2 }, T/ c$ R) Y9 r
3 f* K# Q+ V: ?) K+ M" h/ /多于三个点,划分为两部分 % X$ W! ]' ~. k8 |% h! e8 ]4 S h ' H" B; s. E6 C. V' Oint m = (l+r)/2; // X[l:m] 在A中,余下的在B中" _0 z2 i( H: P: G, ^; {
& u1 W* K$ x; v- D
// 在Z[l:m] 和Z [ m + 1 : r ]中创建按y排序的表* G* G7 v* v, o8 x) ^1 b; F
4 w, A- N8 _+ S/ Sint f = l, // Z[l:m]的游标 + k/ s1 s% p0 a9 [8 C2 \# I' P4 | g$ t# i" t9 ~& A8 B
g = m+1; // Z[m+1:r]的游标 6 Y- y& `1 q+ o/ m7 b ) n; y! J) R g9 F' y1 X S1 ufor (int i = l; i <= r; i++) 7 R8 }# m2 B' D5 o# K8 ~ ! o# O3 n# ~% g1 wif (Y.p > m) Z[g++] = Y;0 b) p6 l# X# O2 a* j% |
( c6 ~7 [, f7 G. ?) n
else Z[f++] = Y; 6 Q8 e" o2 [4 U$ {. v# _ ( J. K& r1 ~& S6 R+ a1 [// 对以上两个部分进行求解* Y# ?( P4 C0 Y
; a: [- C8 v. dc l o s e ( X , Z , Y, l , m , a , b , d ) ;4 B; U7 X2 g1 ^
( B4 `8 D+ i6 y! I, S% D, b: l" G
float dr;/ j/ W, d Z2 E: N' m9 N
: P, f& Z: h9 u. `6 \6 LPoint1 ar, br;! e# F6 d4 N2 Q- f' `+ k2 h
4 V3 O8 T) M8 z a. Sc l o s e ( X , Z , Y, m + 1 , r, a r, b r, d r ) ; : q9 Q8 ~8 G8 F4 Y 6 m9 J8 G8 V/ g// (a,b) 是两者中较近的点对' d3 c% S( M6 r0 r% N1 f6 [3 d
) Z2 F1 |0 W" @* u" Wif (dr < d) {a = ar; 0 h. F5 Y3 }1 z( B1 k . I- P; b8 i: e5 ]1 |7 A6 z+ e# x7 Bb = br; + a5 g! c# r! w' l8 s+ T5 M 0 e' D, \) d5 H* b8 bd = dr;} ' F4 X. x) |0 y5 A# T+ k% E+ K3 ?/ u' y/ i- Z/ [6 |/ q- [) X& L
M e r g e ( Z , Y,l,m,r);// 重构Y & b: L: R( H# s* `3 e* q $ X* n& I5 v o( D. d) W/ /距离小于d的点放入Z * k( n& C& f* k$ f" B8 A/ | " s6 I- ]* F7 Mint k = l; // Z的游标4 p/ R I2 _! l& a
0 \6 f6 T& z; s. b; e; sfor (i = l; i <= r; i++) 5 W+ I9 z& Y. s$ K% V ) h- i7 B' p& _0 n* f; ~if (fabs(Y[m].x - Y.x) < d) Z[k++] = Y;9 f0 n p+ R D- h1 ~ \
$ M; i7 G- w2 [4 V, {# l6 n
// 通过检查Z [ l : k - 1 ]中的所有点对,寻找较近的点对7 z( B) H7 Z* Q* ^
# C9 z$ B5 d( X5 q% q- wfor (i = l; i < k; i++){ " d) o0 o) X5 B" l + u; V/ `& V0 z+ M( ]0 zfor (int j = i+1; j < k && Z[j].y - Z.y < d; / Q' ~4 c( L0 E9 C; }9 T+ n ' O w' W$ E4 X+ Sj + + ) {: @# C/ q7 x) N6 |
4 |; p7 _/ G. W9 i1 t
float dp = dist(Z, Z[j]);) M1 m( }" F2 @! v7 Y9 `+ w
- `/ u# {, N' m% c
if (dp < d) {// 较近的点对 4 D) E6 I( g% ^3 _8 r/ I, v7 ~; X& Z$ C0 ?% M" e8 k
d = dp;9 y: i& n/ ` }
' ?( X2 K5 I/ Q: q6 o}1 e' j O. `3 j, T! b
/ }, L& g: S6 v' s/ i5 ?% H
} ! k( D0 H+ U0 l9 P; S: a4 @. c3 @: C7 t5 T7 F b4 p8 [3 w, K$ Q% g
} 2 y8 H# @0 ]2 V+ w; s8 D2 K + |" c, B, {( l/ Z5 Q+ ?4 Z% ?/ {函数c l o s e(见程序1 4 - 11)用来确定X[1:r] 中的最近点对。假定这些点按x 坐标排序。在Y [ 1 : r ]中对这些点按y 坐标排序。Z[ 1 : r ]用来存放中间结果。找到最近点对以后,将在a, b中返回最近点对,在d 中返回距离,数组Y被恢复为输入状态。函数并未修改数组X。7 o# a! Q+ j' ~9 y. c
, y2 [7 i7 j% i/ }) L; S2 @5 Q2 S j首先考察“小问题”,即少于四个点的点集。因为分割过程不会产生少于两点的数组,因此只需要处理两点和三点的情形。对于这两种情形,可以尝试所有的可能性。当点数超过三个时,通过计算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 ]。 % y: U0 n! W" s6 R# s; C! I& G5 f1 X
为实现图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 + t, j8 |- e3 `/ t/ I / r( w0 W6 B" f1 b p5 h( i还是在RB中)与Z[j] 配对来实现,其中i<j 且Z [ j ] . y - Z [ i ] . y< 。对每一个Z [ i ],在2 × 区域内所检查的点如图1 4 - 1 7所示。由于在每个2 × 子区域内的点至少相距。因此每一个子区域中的点数不会超过四个,所以与Z [ i ]配对的点Z [ j ]最多有七个。 0 B/ z$ y# \" s5 W7 J" [8 M/ w. \( Q/ j
2. 复杂性分析4 U5 U5 J# \8 `6 i- p3 D5 [( W+ B8 o7 a( Y
' a1 f% s8 ]6 v* V" j G令t (n) 代表处理n 个点时,函数close 所需要的时间。当n<4时,t (n) 等于某个常数d。当n≥4时,需花费(n) 时间来完成以下工作:将点集划分为两个部分,两次递归调用后重构Y,淘汰距分割线很远的点,寻找更好的第三类点对。两次递归调用需分别耗时t (「n /2ù」和t (?n /2?). 8 y9 O6 b b c2 G; j3 a1 b, K) ?2 R* z/ k- x8 U
这个递归式与归并排序的递归式完全一样,其结果为t (n) = (nl o gn)。另外,函数c l o s e s t还需耗时(nl o gn)来完成如下额外工作:对X进行排序,创建Y和Z,对Y进行排序。因此分而治之最近点对求解算法的时间复杂性为(nl o gn)。</P>