( E( y4 }) c3 z3 u. J9 C8 H% t L- X8 Z: k8 p
if (n较小) {用直接法寻找最近点对/ e$ ~% b$ k' |9 b. f
' c |6 u& G( u+ ?9 y1 D1 G
R e t u r n ; }, a% [2 C8 N5 \2 Q: ~; x1 D
! y( z5 q8 \. m" T8 I q
// n较大 0 a h5 v$ W5 a$ @! a # Z0 [1 ~; h. X( L2 s ^5 e5 n+ a" O将点集分成大致相等的两个部分A和B Z( R$ F" C) D, q/ X7 G$ k2 A) A4 q$ K, n n {0 e" f
确定A和B中的最近点对+ |* r A `# U$ M5 y) s
, ?) P" @7 ?$ K' q; W
确定一点在A中、另一点在B中的最近点对$ v* o" V! A9 w; U, ?
. F% L1 D2 R! i1 U+ m
从上面得到的三对点中,找出距离最小的一对点 ' V. F/ ?- j3 T5 e* b ( E" Q2 x# u; T7 N6 s图14-13 寻找最近的点对 Z6 R6 e% s2 H- S9 Z3 Z, q$ Q 9 R, U- a0 Z- O' ^4 S/ |/ K/ R; U( V7 e N
为了确定第三种情况下的最近点对,需要采用一种不同的方法。这种方法取决于点集是如何被划分成A、B的。一个合理的划分方法是从xi(中间值)处划一条垂线,线左边的点属于A,线右边的点属于B。位于垂线上的点可在A和B之间分配,以便满足A、B的大小。/ ~& a. z j5 ~
( \% X0 ]. d0 V. O0 {- g/ B1. 选择数据结构 ! o5 D- g3 p ]# `& H 7 |& B7 V( H: [9 }! d为了实现图1 4 - 1 3的分而治之算法,需要确定什么是“小问题”以及如何表示点。由于集合中少于两点时不存在最近点对,因此必须保证分解过程不会产生少于两点的点集。如果将少于四点的点集做为“小问题”,就可以避免产生少于两点的点集。0 d$ k+ e6 \ p; x3 w5 w
5 z. I% s' O$ i# W: e# V. k! k3 I每个点可有三个参数:标号, x 坐标,y 坐标。假设标号为整数,每个点可用P o i n t l类(见程序1 4 - 8)来表示。为了便于按x 坐标对各个点排序,可重载操作符<=。归并排序程序如1 4 -3所示。/ |$ b. |( E3 V
; x- O$ A; B8 r$ ]7 N
程序14-8 点类 " g/ b" e. N+ }5 Q4 u: q/ O F* N& Y9 p6 i: oclass Point1 { I. U! K. W/ J2 a
, i5 I$ J: m+ `; x/ Q
friend float dist(const Point1&, const Point1&);9 B% U: W" M+ T8 e. i; K
" F! \. h, |6 I3 nfriend void close(Point1 *, Point2 *, Point2 *, int, int, Point1&, Point1&, float&);" j0 j4 X- @& `9 _6 ~1 g. `/ @
; D- k: p5 r, ~9 z
friend bool closest(Point1 *, int, Point1&, Point1&,float&);5 q/ O( U% j, `" _2 w) D3 I5 s
; ~# a0 y- p2 X
friend void main(); 7 o& q# [1 s, R* K% \ 1 u, D5 M1 R# g* Z5 X9 u4 w" cp u b l i c :9 I4 l+ r" Q2 G9 n
, t3 I. }/ }( `; v: p% j w
int operator<=(Point1 a) const 5 u) u: i `& B4 X! l/ U1 y% o, H( F- O% G
{return (x <= a.x);} : n" V6 F0 E: z( N% _, ~3 K * l9 O! Y/ o, j8 c5 E0 gp r i v a t e :( S5 r3 b2 {2 ?. w+ t( U- |
! n O& ^7 |. e. S$ C& T' y( F
int ID; // 点的编号 8 i- S! g% \% U2 U8 J# x6 ]7 U! F9 j+ r ]6 j4 ?
float x, y; // 点坐标 6 i" }% P$ e& [" A) B ( B% z2 h5 L- O# m, Z" ]3 h} ;% y1 m0 i$ ]; l2 I6 j3 `6 v1 I. J
; j$ U+ ]# f) j" p% X4 B8 q
class Point2 {! M7 I* o" b2 H2 F; {3 @# z- D, B
6 c/ e1 K6 t& x% I9 E$ i6 Cfriend float dist(const Point2&, const Point2&); 4 `7 H. w/ w; @) O/ v' M! N7 ~2 K. I6 p q& p! y
friend void close(Point1 *, Point2 *, Point2 *, int, int, Point1&, Point1&, float&); # Q) M: t# ?; }) m/ K/ j & b1 f5 L) f/ _+ m2 {) ?friend bool closest(Point1 *, int, Point1&, Point1&, float&);& n. B# E. u }) D% q& t6 l( n
! B$ C3 Z: p4 K6 p! ^3 F- |. zfriend void main(); $ k0 G1 E8 r/ E& a6 B' R" c+ z 1 L+ P, C4 M! Op u b l i c : ( w. ~) N) T5 V# {7 o* O% G9 O; l. t- L1 W' W5 f
int operator<=(Point2 a) const, d! j6 k6 o* B2 m4 \# |+ c1 A
* @- l( e# `/ Z5 B* V3 l; D- c; [" m9 f
{return (y <= a.y);} * s( r! M/ D& o4 G6 \' @ : F+ o5 w* }, S/ {6 Ip r i v a t e :- \1 K9 h: p5 H" K" J$ a
' J: N) j5 j; }; R m+ a
int p; // 数组X中相同点的索引 |( s4 t8 X5 Y$ l. e9 W( R6 g! b% y/ T, I4 T
float x, y; // 点坐标 " Z+ z7 ], p# _% @) T ; V5 t* A+ u! `6 ]* i2 ~} ; 0 H$ b3 j7 B) A; y$ X" y2 G; g2 d8 [( R
所输入的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中的对应点。" ^' R& z9 F+ l! t
8 H" a+ E' l) c+ N3 w' j确定了必要的数据结构之后,再来看看所要产生的代码。首先定义一个模板函数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类的友元。/ ^* K; S5 H! j) t
6 a5 ^1 L+ F% A; v! V+ \0 U" F
程序14-9 计算两点距离& {$ x% G, R4 d
5 C$ V& J" s7 w1 x @6 b
template<CLASS T>' p2 @$ _) B! ^3 i3 T4 H3 d3 C6 u
1 s& c% X; q2 }1 ~+ P0 G
inline float dist(const T& u, const T& v) 8 w/ f% ^% U* K% ]- d/ z @ 4 i9 [- k3 D, S- _{ / /计算点u 和v之间的距离 ( d% k: f5 ?3 A% v9 h- ] & J: w( E& a% }) V7 k6 {8 ?/ Bfloat dx = u.x-v. x ; 5 M/ X9 X: C7 ~& b* E 4 k5 \3 B! ^" K3 rfloat dy = u.y-v. y ; , G# m( }2 \8 \2 f( M# P& V ( q4 @ z7 A% ^ F1 O4 w- Nreturn sqrt(dx * dx + dy * dy); 5 K& |/ S3 Y0 F# X1 \8 t2 ^2 `! _7 r7 N/ y% I, `' q
} f8 n6 v9 ~, ^9 d 7 u4 N1 I( b% 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 ),该函数实际求解最近点对。 ( U: y0 D7 r# D2 e$ a2 W $ P2 n3 X! x7 |" A0 a$ c& X V程序14-10 预处理及调用c l o s e y8 B3 Q% W1 D& v) n/ L& {
" x( }9 G1 i+ R& [! P
bool closest(Point1 X[], int n, Point1& a, Point1& b, float& d)4 b% c; J: Z# e4 F; J" [
6 w8 ]- B3 }% v; I1 Z
{// 在n >= 2 个点中寻找最近点对 / A2 l7 z" v- H9 ^7 J9 ^+ s4 Z. k5 l+ K% u: |- L F! v
// 如果少于2个点,则返回f a l s e& N+ P4 g4 [" z2 W) R
2 Y; e9 o" \- J1 L$ nM e r g e S o r t ( X , n ) ; ; O3 V/ `6 m! m) G) u: g : k( {! t/ Y* O' D ?2 ?# r3 V// 创建一个按y坐标排序的点数组& P' n7 q$ ?+ V- V8 H7 d0 ^% f
; u; G( S t3 V. yPoint2 *Y = new Point2 [n]; ; J7 j8 u1 z# V3 R0 T9 R4 ~ & j8 _ G. p" S. i! _- A* y5 ]for (int i = 0; i < n; i++) { + j4 d4 o3 k/ T+ G( L" g, z" C& D; A& R. c Y
// 将点i 从X 复制到Y 6 o) i4 ~& Z ]- l/ _, I6 ` & o! X V2 X, {6 a# n9 YY.p = i;9 |$ h& o- D4 E3 X! O
( j( K0 b$ H1 b" BY.x = X.x;" B O) X5 Y7 u& j
) N7 v. N3 I! g9 x, @1 H) y& O
Y.y = X.y;( T* ]) h& H1 U& \7 D: b
" |. D: J: a) z0 a2 A5 o; b
} 6 \$ ^# b8 G+ ?* n) G. x* D) Y& S2 g. e# h1 D! y' w) u1 I7 {
M e r g e S o r t ( Y,n); // 按y坐标排序& r- l8 L, L+ U/ J0 Y! J& X
: K' l) D7 f r// 创建临时数组! J, p7 v7 g! P, v$ J
/ {! Z* j) u7 v1 Z0 [Point2 *Z = new Point2 [n]; ~% e6 r, \/ ~( f: n: L. n
2 i$ n5 P- t8 T, V8 F* s9 p// 寻找最近点对# Y# a, |& W1 N! j: |2 V) q, O1 M& ]
- C+ i$ y7 ?- o. t; H2 X0 c
c l o s e ( X , Y, Z , 0 , n - 1 , a , b , d ) ; # Q( S4 Y. h) P. X k: |6 v; u1 Y2 q! V; C. D
// 删除数组并返回 - p& m) {4 w/ _+ n1 R# N8 h 6 V; D4 J1 [- I/ C) Gdelete [] Y; 5 G; W" _# G4 X! |- p) e$ B- s/ z$ L1 u; ?( _9 j. a$ ]4 Q
delete [] Z; 6 j% Q9 x# r6 }+ h 4 g# d6 ~$ o$ c& u" r% B3 U# Vreturn true;- R* x/ m8 ?% n0 {9 R# m) j0 N! C
; h: g6 f. t& j: W% g, R
}5 @/ F, y2 G# ~; X1 ]
! q4 J' q5 z. l6 ~- o$ Efloat d3 = dist(X[l], X[r]);* y! b" `" i. X" ~2 ]' x
, |9 _/ u, \) [0 i- n// 寻找最近点对( p3 F' u- K9 P6 c
3 |6 p) v, n# O. \7 _5 R4 Sif (d1 <= d2 && d1 <= d3) { & W. o# O/ S/ e- @# a- H1 @4 g ( k8 Z( m2 n/ ^$ ~( E* na = X[l];5 W& |2 J) N9 u) R5 u* k, @+ [
* S3 {. I7 e M+ Y" Z" ]
b = X[l+1]; 7 w. V- ~6 C" H: A9 G$ t% K$ j8 }6 x: p& O
d = d1;" e4 u, M- h5 W) Y, D
! D5 b; T" [4 `/ a0 j
r e t u r n ; }- m: g3 K3 J3 w; h: e
; U8 w+ O! H' o4 a2 ?9 q
if (d2 <= d3) {a = X[l+1];7 f! H" I- w. J. l3 z: Y; ?
& u0 I+ ? H6 \1 `b = X[r]; ) |1 k4 C6 x* v( U. X 4 q+ n: j2 x) j3 D. k9 F$ fd = d2;}: o8 R- l$ R6 V) X8 L
+ j4 f _# o+ g1 C/ Q9 K2 Ielse {a = X[l]; 1 ?5 o1 ^6 t8 k6 g ' R5 D5 c) I" I; Yb = X[r];5 G/ t) S: y [4 o! u/ X: k
# A& G* H$ P1 W1 o4 h! q5 ^d = d3;} / e; o) V- F) @8 `) j- L. y6 T4 L+ x9 {; ?! h" N* v. U" N
r e t u r n ; } ! `$ P1 O1 ^( n+ z$ p ' e3 N5 O* [. J" A% V/ _+ r/ /多于三个点,划分为两部分 ; _* c0 S# I5 I, e/ ]3 k0 N + B: Q2 S: q' v( C( Yint m = (l+r)/2; // X[l:m] 在A中,余下的在B中, l/ _ P; _5 f/ Y* u
E/ r( c. G- V! Q) z, a; Z
// 在Z[l:m] 和Z [ m + 1 : r ]中创建按y排序的表4 C2 j; c c% D. g
- Y/ j2 B, l+ Q2 a% L( Kint f = l, // Z[l:m]的游标 ! n3 R/ S4 b9 ?5 G o& x( C; H/ t/ T
g = m+1; // Z[m+1:r]的游标 % G' h' t1 P; N/ `8 @# N3 J I4 R$ x: s
for (int i = l; i <= r; i++) - ^6 u9 c8 S* c, }( O, e( I% l9 W; i/ P2 y
if (Y.p > m) Z[g++] = Y; 2 q6 ^( w: B$ J* i5 K6 {" w1 B5 d* g2 d+ G$ }
else Z[f++] = Y;8 z3 p1 N, ~! W) [6 X9 w5 l
5 ?! c$ L* B" }' t/ s9 s/ O// 对以上两个部分进行求解; V3 Z* e7 z! R: F- U
% H6 W1 I6 O2 e, L" [$ c6 X
c l o s e ( X , Z , Y, l , m , a , b , d ) ;# ^8 f$ ~+ o0 n ?7 G8 Q
) _% p# k1 y" `) g e9 sPoint1 ar, br; + x8 o4 f9 y; S 7 n* @- S2 x+ H8 X( E0 ?# Sc l o s e ( X , Z , Y, m + 1 , r, a r, b r, d r ) ;3 I& E2 M* E! t- ~% ]6 T
; @6 H* Q( |% c1 X- h// (a,b) 是两者中较近的点对9 M! j' I4 `! y; o
- H7 a: H2 b& W$ M' s" ?+ T
if (dr < d) {a = ar;7 ]" O0 x; u& C w: y
% w. h2 F$ N; @, k+ K2 B+ E
b = br; " u1 `% l5 \8 K U$ C$ q8 ~2 T# G( W0 [% q
d = dr;}6 ]9 n8 l0 x, x7 ]7 V
( w! s. L6 s+ E
M e r g e ( Z , Y,l,m,r);// 重构Y : Q, Y ?' R( H& n; _8 ?" t. s 4 d; c l1 w" W) m8 p/ /距离小于d的点放入Z$ C3 L2 O* m$ m+ {
) \- y( C& p( ~$ y! e' hint k = l; // Z的游标" l* U4 Y" i) y7 @- ~
6 I+ |2 x& }9 w" x) e, z9 Yfor (i = l; i <= r; i++)/ D' t' U6 m9 h, f5 }
" F/ k/ E/ V7 W' D1 w( z% I8 p5 W
if (fabs(Y[m].x - Y.x) < d) Z[k++] = Y; ; r/ o1 m0 h) d7 S 5 S; D* n9 r0 v( Q( c// 通过检查Z [ l : k - 1 ]中的所有点对,寻找较近的点对 : N. ~9 h0 t# A0 l) z4 T+ n; u/ ?. a6 F% h
for (i = l; i < k; i++){. j H# J* O. B a! N. z: B
1 w0 y% A) P! @( O" P8 `2 i
for (int j = i+1; j < k && Z[j].y - Z.y < d;" s# r: H+ A$ b# `; p( Z0 }8 p" I
; a# y: c- u+ J
j + + ) { 8 {# I4 \3 q J l4 p % M ?3 ]9 ~ T) o B& Tfloat dp = dist(Z, Z[j]); ' ^' }+ ^* D9 n- [8 u9 L" [9 T( W& ^5 K* d
if (dp < d) {// 较近的点对' V4 r; ~) q5 T: p5 V
+ l q: `- p7 y1 Gd = dp; / Z9 F$ {0 S/ c: C: |; c % I) i0 e! w4 x8 l+ f# \a = X[Z.p];! m# K3 r6 C4 M7 S# b! V
; p- w* ^# V! _, z8 p! b
b = X[Z[j].p];}/ C9 N! Z! i9 G6 _8 Q7 B) X
! b* V; w8 `2 M' i* w, p4 d) j
}# A) K {0 x) _$ _
7 j$ D: ?" n$ R4 ?3 w1 o
} B) B6 a5 P* _# ^* ~! A# @* p
( H2 o' w: w5 p1 R+ T7 e3 d5 |}' ?4 o% @# n& n# h
# d2 j( ]! X9 @- Y% S函数c l o s e(见程序1 4 - 11)用来确定X[1:r] 中的最近点对。假定这些点按x 坐标排序。在Y [ 1 : r ]中对这些点按y 坐标排序。Z[ 1 : r ]用来存放中间结果。找到最近点对以后,将在a, b中返回最近点对,在d 中返回距离,数组Y被恢复为输入状态。函数并未修改数组X。9 g- E+ }: a* b# O% L8 w9 |
, z$ j& V7 d, }0 N- U. q首先考察“小问题”,即少于四个点的点集。因为分割过程不会产生少于两点的数组,因此只需要处理两点和三点的情形。对于这两种情形,可以尝试所有的可能性。当点数超过三个时,通过计算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 ]。 % }4 r. t. O" K1 a* ^6 L + v9 r. n3 w) `2 C' I为实现图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 u) o6 H& K0 {9 m v& c0 m 9 s2 o, ~4 j0 c4 E还是在RB中)与Z[j] 配对来实现,其中i<j 且Z [ j ] . y - Z [ i ] . y< 。对每一个Z [ i ],在2 × 区域内所检查的点如图1 4 - 1 7所示。由于在每个2 × 子区域内的点至少相距。因此每一个子区域中的点数不会超过四个,所以与Z [ i ]配对的点Z [ j ]最多有七个。 6 C" m- o8 B; |4 Y * h8 G( H, {/ q5 x2. 复杂性分析6 O5 P5 Z# N8 i" L- D0 r
* L3 X" s. E: _, Q5 q: R令t (n) 代表处理n 个点时,函数close 所需要的时间。当n<4时,t (n) 等于某个常数d。当n≥4时,需花费(n) 时间来完成以下工作:将点集划分为两个部分,两次递归调用后重构Y,淘汰距分割线很远的点,寻找更好的第三类点对。两次递归调用需分别耗时t (「n /2ù」和t (?n /2?). * Q$ m3 @- \9 Z% R7 \( ]6 h5 n$ t5 m$ l% @9 Z8 k3 i3 m0 q3 o( U3 `. m
这个递归式与归并排序的递归式完全一样,其结果为t (n) = (nl o gn)。另外,函数c l o s e s t还需耗时(nl o gn)来完成如下额外工作:对X进行排序,创建Y和Z,对Y进行排序。因此分而治之最近点对求解算法的时间复杂性为(nl o gn)。</P>