<>可以运用分而治之方法来解决排序问题,该问题是将n 个元素排成非递减顺序。分而治之方法通常用以下的步骤来进行排序算法:若n 为1,算法终止;否则,将这一元素集合分割成两个或更多个子集合,对每一个子集合分别排序,然后将排好序的子集合归并为一个集合。 " n3 H$ @$ T& X4 a9 L% r9 u% s 8 O+ s5 P% c2 ?( x. M假设仅将n 个元素的集合分成两个子集合。现在需要确定如何进行子集合的划分。一种可能性就是把前面n- 1个元素放到第一个子集中(称为A),最后一个元素放到第二个子集里(称为B)。按照这种方式对A递归地进行排序。由于B仅含一个元素,所以它已经排序完毕,在A排完序后,只需要用程序2 - 1 0中的函数i n s e r t将A和B合并起来。把这种排序算法与I n s e r t i o n S o r t(见程序2 - 1 5)进行比较,可以发现这种排序算法实际上就是插入排序的递归算法。该算法的复杂性为O (n 2 )。把n 个元素划分成两个子集合的另一种方法是将含有最大值的元素放入B,剩下的放入A中。然后A被递归排序。为了合并排序后的A和B,只需要将B添加到A中即可。假如用函数M a x(见程序1 - 3 1)来找出最大元素,这种排序算法实际上就是S e l e c t i o n S o r t(见程序2 - 7)的递归算法。; k3 s3 r4 y/ l6 A9 H
3 a" v1 s1 ]) J) Z' {& i ]4 y0 l
假如用冒泡过程(见程序2 - 8)来寻找最大元素并把它移到最右边的位置,这种排序算法就是B u b b l e S o r t(见程序2 - 9)的递归算法。这两种递归排序算法的复杂性均为(n2 )。若一旦发现A已经被排好序就终止对A进行递归分割,则算法的复杂性为O(n2 )(见例2 - 1 6和2 - 1 7)。1 f; v( |# p# C3 Y
& d$ F' m E2 T上述分割方案将n 个元素分成两个极不平衡的集合A和B。A有n- 1个元素,而B仅含一个元素。下面来看一看采用平衡分割法会发生什么情况: A集合中含有n/k 个元素,B中包含其余的元素。递归地使用分而治之方法对A和B进行排序。然后采用一个被称之为归并( m e rg e)的过程,将已排好序的A和B合并成一个集合。5 A# y) @/ O( U9 ]6 J9 L3 Q
" [$ F" E: V! _* l
例2-5 考虑8个元素,值分别为[ 1 0,4,6,3,8,2,5,7 ]。如果选定k = 2,则[ 1 0 , 4 , 6 , 3 ]和[ 8 , 2 , 5 , 7 ]将被分别独立地排序。结果分别为[ 3 , 4 , 6 , 1 0 ]和[ 2 , 5 , 7 , 8 ]。从两个序列的头部开始归并这两个已排序的序列。元素2比3更小,被移到结果序列;3与5进行比较,3被移入结果序列;4与5比较,4被放入结果序列;5和6比较,.。如果选择k= 4,则序列[ 1 0 , 4 ]和[ 6 , 3 , 8 , 2 , 5 , 7 ]将被排序。排序结果分别为[ 4 , 1 0 ]和[ 2 , 3 , 5 , 6 , 7 , 8 ]。当这两个排好序的序列被归并后,即可得所需要的排序序列。 & A/ N( A; h2 K- W h" h( K* r, ]
图2 - 6给出了分而治之排序算法的伪代码。算法中子集合的数目为2,A中含有n/k个元素。 ! j$ a! B7 O( K! A# ?. ~2 M / F' H' v% ~5 U. r4 ztemplate<CLASS T> 9 y, m, h- s" [8 R. g; j) s* ]' ~; w3 P8 [& P- X X
void sort( T E, int n)/ o! y" H, l+ `. s
/ r: Y5 F! U7 o* \8 f# z
{ / /对E中的n 个元素进行排序, k为全局变量 ' M4 q5 \% i: d/ V# Q3 X - _" T2 P+ t/ A8 G& b" }2 e7 Aif (n >= k) { 4 Z& h; ?& u& O, Z2 y' ]9 {7 F4 s( P o6 w6 d4 T1 ?
i = n/k; 5 k( A# y. t7 l# u. j) t+ r/ I3 j2 G" c
j = n-i; Q$ U8 v/ P/ u2 X
, ^ G% J9 D; a( _' S# K% y6 R令A 包含E中的前i 个元素 : N0 L) m2 D8 [" Q0 Z % c2 Q0 q# h" K& a! w) ?令B 包含E中余下的j 个元素 3 R: X0 U, h; ]4 |. D4 l 3 m* h9 _* c- o+ r0 w) ds o r t ( A , i ) ;8 B, X' Q% I L( h# O# K6 {
1 ?9 i/ ]- \3 n5 H( }s o r t ( B , j ) ; 9 H; q% B1 Q- e, T) y8 H5 Q# p0 }) y2 j, n0 m# M. g6 ?
m e rge(A,B,E,i,j,); //把A 和B 合并到E ' c, {+ c+ V4 Y! c * v$ S1 w! v* ~& G}4 N+ c& b8 K1 B5 b2 N! R4 A
1 `4 H E( D p ( D0 _; p" Z R: d6 O' F: I& P从对归并过程的简略描述中,可以明显地看出归并n个元素所需要的时间为O (n)。设t (n)为分而治之排序算法(如图1 4 - 6所示)在最坏情况下所需花费的时间,则有以下递推公式: 5 r- U, M7 w% d# ?4 ? ; m1 Q* ^+ Y+ c0 h其中c 和d 为常数。当n / k≈n-n / k 时,t (n) 的值最小。因此当k= 2时,也就是说,当两个子集合所包含的元素个数近似相等时, t (n) 最小,即当所划分的子集合大小接近时,分而治之算法通常具有最佳性能。: ~0 [0 k( x9 T7 v
u9 U3 p; C5 s p. Z$ F可以用迭代方法来计算这一递推方式,结果为t(n)= (nl o gn)。虽然这个结果是在n为2的幂时得到的,但对于所有的n,这一结果也是有效的,因为t(n) 是n 的非递减函数。t(n) =(nl o gn) 给出了归并排序的最好和最坏情况下的复杂性。由于最好和最坏情况下的复杂性是一样的,因此归并排序的平均复杂性为t (n)= (nl o gn)。 # V2 T/ s' @$ q 9 I4 e# B0 S% o8 c7 G图2 - 6中k= 2的排序方法被称为归并排序( m e rge sort ),或更精确地说是二路归并排序(two-way merge sort)。下面根据图1 4 - 6中k= 2的情况(归并排序)来编写对n 个元素进行排序的C + +函数。一种最简单的方法就是将元素存储在链表中(即作为类c h a i n的成员(程序3 -8))。在这种情况下,通过移到第n/ 2个节点并打断此链,可将E分成两个大致相等的链表。- x5 E u6 z4 W9 l: ~9 V. V- I0 M
9 @0 [) n3 o4 h, F 7 f7 A( m. C5 u" x3 m5 V5 h+ U# }1 p" @6 atemplate<CLASS T>" x: a" l {' }9 @4 r1 C8 U" `
0 H0 f) C' u6 R1 @$ b! [
M e rgeSort( T a[], int left, int right)' T$ @& A/ O* X+ k w
$ G4 B% K9 p) d/ G0 t
{ / /对a [ l e f t : r i g h t ]中的元素进行排序 & B/ q; A' k' ~1 m( A* A# x. }$ ^' k! Z: n; \; w2 _
if (left < right) {//至少两个元素6 l a& U( @; r; D
( q% B7 p5 J( A) @( E1 v* v
int i = (left + right)/2; //中心位置 , j. \$ t |# A$ ?, u# T8 |& e / v2 l. u: @1 _" G3 i* YM e rgeSort(a, left, i);% @; e/ M1 u* h8 b9 |
# i. b% p2 C. K( q k" B
M e rgeSort(a, i+1, right); ' J, c/ y8 \+ l& b * I$ t$ j% ]. bM e rge(a, b, left, i, right); //从a 合并到b' L* O& @$ B( T; n5 _
* K4 H6 H9 M, tCopy(b, a, left, right); //结果放回a2 Y7 T, h) w; F7 [. O9 U