<>可以运用分而治之方法来解决排序问题,该问题是将n 个元素排成非递减顺序。分而治之方法通常用以下的步骤来进行排序算法:若n 为1,算法终止;否则,将这一元素集合分割成两个或更多个子集合,对每一个子集合分别排序,然后将排好序的子集合归并为一个集合。3 [. o# n7 {& I$ ^' G
# N" W4 A. b- k( O( i假设仅将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)的递归算法。 , k6 O% c! `" @0 ~* m/ r) _8 z1 b7 |. A
假如用冒泡过程(见程序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)。: e3 k0 b6 b5 D3 S( \
! _) \8 ~7 C4 ?/ _
上述分割方案将n 个元素分成两个极不平衡的集合A和B。A有n- 1个元素,而B仅含一个元素。下面来看一看采用平衡分割法会发生什么情况: A集合中含有n/k 个元素,B中包含其余的元素。递归地使用分而治之方法对A和B进行排序。然后采用一个被称之为归并( m e rg e)的过程,将已排好序的A和B合并成一个集合。% y& q7 l! X% e9 W
/ I- R: D8 b( W/ T3 Y6 O. Y \/ G. o8 h9 g
从对归并过程的简略描述中,可以明显地看出归并n个元素所需要的时间为O (n)。设t (n)为分而治之排序算法(如图1 4 - 6所示)在最坏情况下所需花费的时间,则有以下递推公式:; d, h) ]7 ]3 P b& m( j9 k
" i* j. z7 f2 Q7 b2 |) M6 v2 ?7 S
其中c 和d 为常数。当n / k≈n-n / k 时,t (n) 的值最小。因此当k= 2时,也就是说,当两个子集合所包含的元素个数近似相等时, t (n) 最小,即当所划分的子集合大小接近时,分而治之算法通常具有最佳性能。2 o5 g0 o# i7 P3 l) s' I
8 W/ t2 n+ }) F1 V
可以用迭代方法来计算这一递推方式,结果为t(n)= (nl o gn)。虽然这个结果是在n为2的幂时得到的,但对于所有的n,这一结果也是有效的,因为t(n) 是n 的非递减函数。t(n) =(nl o gn) 给出了归并排序的最好和最坏情况下的复杂性。由于最好和最坏情况下的复杂性是一样的,因此归并排序的平均复杂性为t (n)= (nl o gn)。 5 s& F5 E' a$ k4 L5 c / ?& B4 |5 h* X, I1 u图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分成两个大致相等的链表。 ( a1 @0 K( s) v- ~/ c& p% R& a, I! x8 x1 ]1 o0 g
归并过程应能将两个已排序的链表归并在一起。如果希望把所得到C + +程序与堆排序和插入排序进行性能比较,那么就不能使用链表来实现归并排序,因为后两种排序方法中都没有使用链表。为了能与前面讨论过的排序函数作比较,归并排序函数必须用一个数组a来存储元素集合E,并在a 中返回排序后的元素序列。为此按照下述过程来对图1 4 - 6的伪代码进行细化:当集合E被化分成两个子集合时,可以不必把两个子集合的元素分别复制到A和B中,只需简单地在集合E中保持两个子集合的左右边界即可。接下来对a 中的初始序列进行排序,并将所得到的排序序列归并到一个新数组b中,最后将它们复制到a 中。图1 4 - 6的改进版见图1 4 - 7。' B! I- j$ b! r4 t" X2 ]
2 o W h, B- K/ B% m+ m: |' @; o2 O0 l7 h H' q4 u& H2 G$ K+ s9 \+ R) `
$ S* u+ X8 w/ Q: |1 h3 Xtemplate<CLASS T>- w% y& ]. a$ ^1 |
8 A9 Y' y1 ^9 e$ S5 D6 O* [
M e rgeSort( T a[], int left, int right) % |$ ^" t b6 l& z . m- q" p9 ]6 b* ]. m2 _{ / /对a [ l e f t : r i g h t ]中的元素进行排序: B- G4 Q$ I6 Y, h0 _( \
h: D X, b% V- o: \
if (left < right) {//至少两个元素* E0 f5 z9 R8 M4 k3 X
Z1 ^1 v8 s i' Y/ O) j$ ]int i = (left + right)/2; //中心位置 # t3 @7 k) A+ Q& r$ z; j9 v9 n% Y6 h4 L( Q; Q
M e rgeSort(a, left, i); 6 Q$ W& ]2 y' `: f; n. K' M2 U' E2 U2 K
M e rgeSort(a, i+1, right);5 j' z# b4 e% C% n6 ?! j# \
( m. ~5 P) d& [+ S: S! \) T
M e rge(a, b, left, i, right); //从a 合并到b0 z/ f5 I3 M4 {* `" D# M W
# O6 A j4 p2 W' S! \: yCopy(b, a, left, right); //结果放回a0 r3 M0 x; s6 M& o8 m
' A/ @# h' L1 A5 |) _8 N
} * d4 J* h. l; j' [, W4 ] t/ Q % P/ b# ]/ ~. c: f4 n} 3 X! N; M& ~6 h( C, h: u9 Q m" C& ?) d+ L
图14-7 分而治之排序算法的改进# U7 H. j% K" {) B% J+ e& A$ c
; \+ a' R$ m k9 Z( D9 ~ |; M3 v! B \# p9 O' W0 D" W
& s# |3 f$ R1 p5 U# W, K6 EMergePass(b, a, s, n); // 从b 归并到a. [& Z5 _( J$ v% F1 h
: x% ^, f9 |0 _. `( |' S: K# w
s += s;3 k; R. a1 K' \ O& v9 {
8 [! N8 F- O, F& X. s
}# V4 r* ?, T5 x. l
! W5 J* K! |; I1 \} # |7 M# s* T3 g( [8 W9 H & G8 k# F A2 p: M, K( B4 }为了完成排序代码,首先需要完成函数M e rg e P a s s。函数M e rg e P a s s(见程序1 4 - 4)仅用来确定欲归并子序列的左端和右端,实际的归并工作由函数M e rg e (见程序1 4 - 5 )来完成。函数M e rg e要求针对类型T定义一个操作符< =。如果需要排序的数据类型是用户自定义类型,则必须重载操作符< =。这种设计方法允许我们按元素的任一个域进行排序。重载操作符< =的目的是用来比较需要排序的域。" e) N! M' T# v