<>可以运用分而治之方法来解决排序问题,该问题是将n 个元素排成非递减顺序。分而治之方法通常用以下的步骤来进行排序算法:若n 为1,算法终止;否则,将这一元素集合分割成两个或更多个子集合,对每一个子集合分别排序,然后将排好序的子集合归并为一个集合。 ; E5 I0 `2 v6 S) n% Z3 Z/ h/ E9 r6 M! f- c- v; E
假设仅将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)的递归算法。 4 p9 ]# A6 U% K) f# o5 @9 u 5 i9 V3 H8 ]. C1 L0 y* ~3 }. z假如用冒泡过程(见程序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 Z8 ^7 E6 v% a8 V/ H! E8 I, S' _/ W $ i0 [8 R8 A: O* E( t+ @' d上述分割方案将n 个元素分成两个极不平衡的集合A和B。A有n- 1个元素,而B仅含一个元素。下面来看一看采用平衡分割法会发生什么情况: A集合中含有n/k 个元素,B中包含其余的元素。递归地使用分而治之方法对A和B进行排序。然后采用一个被称之为归并( m e rg e)的过程,将已排好序的A和B合并成一个集合。 ( j) y$ W2 X$ u! m# j' Q E, x( X3 ~
例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 ]。当这两个排好序的序列被归并后,即可得所需要的排序序列。 9 C/ w! b8 S g0 Z0 Z% {1 K6 F , a' J1 A4 J9 S/ r6 @% d图2 - 6给出了分而治之排序算法的伪代码。算法中子集合的数目为2,A中含有n/k个元素。) p2 h8 R# ]5 _. s2 [, Z: v" H
( |) h* |* w) `% y5 u! n& L6 D
template<CLASS T> ( }* [, U$ m% E: U. w l6 g( C* M' I/ L7 ^void sort( T E, int n) " d: [. a' t& I2 f6 X( ?1 a' d% s A3 D0 W7 l5 {- G
{ / /对E中的n 个元素进行排序, k为全局变量6 m- z6 C6 X, J6 Q: F) }- c" T
. r1 a" v7 _9 ~& d& u' h: ]7 oif (n >= k) { a8 E- x9 F; v0 [3 L& Y) P& \4 H" N8 P0 b7 m1 u& a2 N. [; O
i = n/k; 9 z7 o+ B/ b) |( e8 G$ P5 U/ ~$ `& M* {
j = n-i;3 Y) |0 D* F: E* W7 y0 R# F5 ]3 u
2 t4 W5 y6 t) Y, L' I
令A 包含E中的前i 个元素 : d7 J! w3 A! z4 Q5 g4 U b( s ' s: |& e7 n I. u1 V6 i令B 包含E中余下的j 个元素- H: u) l- w4 ~2 S3 ` o" C* m8 ~
+ {% E9 W, a, @& O
s o r t ( A , i ) ;0 Y3 ?3 [4 h6 I1 @
$ u1 o: ~+ @+ F) o, q9 u; k, p# J# _s o r t ( B , j ) ; / _& G2 h: l+ L3 @. H ) A& {, ?0 D% S. i8 }m e rge(A,B,E,i,j,); //把A 和B 合并到E % C) `! n/ r. A- t a0 {. s7 d& V5 t& F. t' p
}5 I: [, J: n' f8 ]/ @% j
9 V( E7 a" Z# q8 lelse 使用插入排序算法对E 进行排序: i. W5 }- d( H& o+ {5 j
1 F' U7 {) [' d4 h; ?
} ! C3 j' s r2 g* g* h. c, h + s$ E2 Q- P3 o* J; ]6 m+ t图14-6 分而治之排序算法的伪代码2 Q9 Q6 g8 F1 e% D' c
# }1 ^# r" o# ?9 x* i其中c 和d 为常数。当n / k≈n-n / k 时,t (n) 的值最小。因此当k= 2时,也就是说,当两个子集合所包含的元素个数近似相等时, t (n) 最小,即当所划分的子集合大小接近时,分而治之算法通常具有最佳性能。 ) ?" a; T' K& a! b2 ?- F, ~ " H/ b/ t( s' W) C- u可以用迭代方法来计算这一递推方式,结果为t(n)= (nl o gn)。虽然这个结果是在n为2的幂时得到的,但对于所有的n,这一结果也是有效的,因为t(n) 是n 的非递减函数。t(n) =(nl o gn) 给出了归并排序的最好和最坏情况下的复杂性。由于最好和最坏情况下的复杂性是一样的,因此归并排序的平均复杂性为t (n)= (nl o gn)。& p$ o2 ]8 y* i8 I" a0 O
2 _4 @& H1 Q0 T& W% O图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分成两个大致相等的链表。 : g/ N/ g: S+ r! N, Z9 ^! k% I0 |) {8 |7 P
归并过程应能将两个已排序的链表归并在一起。如果希望把所得到C + +程序与堆排序和插入排序进行性能比较,那么就不能使用链表来实现归并排序,因为后两种排序方法中都没有使用链表。为了能与前面讨论过的排序函数作比较,归并排序函数必须用一个数组a来存储元素集合E,并在a 中返回排序后的元素序列。为此按照下述过程来对图1 4 - 6的伪代码进行细化:当集合E被化分成两个子集合时,可以不必把两个子集合的元素分别复制到A和B中,只需简单地在集合E中保持两个子集合的左右边界即可。接下来对a 中的初始序列进行排序,并将所得到的排序序列归并到一个新数组b中,最后将它们复制到a 中。图1 4 - 6的改进版见图1 4 - 7。 6 A2 x$ f3 ~: S- {- d1 H) P6 F G4 s: m0 Z/ Z
' Q* @2 ?/ p! p# u
2 q: |3 f& E3 ]; S8 mtemplate<CLASS T>' ^3 }" M5 _) Z
( t K" P( G: S
M e rgeSort( T a[], int left, int right) $ f7 E% l2 X6 v/ _/ ?0 k% T) }% C5 H3 Y5 C1 a- G: e2 m
{ / /对a [ l e f t : r i g h t ]中的元素进行排序 5 p4 C: K# c, r! c- M6 b( @2 j6 Q* u
if (left < right) {//至少两个元素 " o# P8 W0 {3 K' U( h3 w4 U+ x* V3 s
int i = (left + right)/2; //中心位置 9 c" h, j3 C. H3 Z k# V( n 2 F- f' \8 h" v3 P8 W. {7 M) o( @M e rgeSort(a, left, i); % G% z, O. S" {2 u0 ?4 G8 T+ H' ]" l
M e rgeSort(a, i+1, right);" p5 H- |4 n$ U% G7 y1 V1 A5 L) L: m; k3 `
. L; L8 o- p3 ^6 z& _# wM e rge(a, b, left, i, right); //从a 合并到b ; Z9 J6 ~& q+ |$ p 1 C9 _" N1 x1 o+ U7 E; I sCopy(b, a, left, right); //结果放回a 2 ~0 s U# |/ {$ B$ t 1 E: A4 L& Y" t- Q! N: h" F: W& ~}7 F& j# G9 E# g/ Q' \9 d+ D, P3 T
; d- g% t, L9 {0 t; v; [0 l% Q+ SMergePass(b, a, s, n); // 从b 归并到a : c' O! R6 L, F. e" ]/ B; p4 y% L! k/ s* O0 b' }* `% {: R0 n
s += s; ~7 x, r/ m. ~ # U$ L& u+ ?0 h3 U) J}; O4 S$ K* x R" ^
* K' r* L0 V: Y) D, j T! r$ P
} * U9 ]* M3 \& A/ i / V) ~( S8 \, U/ f为了完成排序代码,首先需要完成函数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 U) ?& X, v w: F3 s( F
: i7 f6 k; t& v) {程序14-4 MergePass函数 3 A) S) h; l% D7 f; Q ' V7 ?7 K, Y' t9 S8 [template<CLASS T> $ A1 V' [! Q" E, W- c4 V; G& _! l7 n0 \
void MergePass(T x[], T y[], int s, int n)" P9 J, h& E `, {% ]
& e0 m/ v8 R( Q# S5 R+ \{// 归并大小为s的相邻段9 U2 b( H+ _0 U% J; x
! y8 w' u8 ]1 n. Z4 Lint i = 0;. O% v5 r0 |) y0 g0 T6 G
9 c7 `$ ~. b& {9 X" z# d
while (i <= n - 2 * s) {, Q- X% [# |, M. Y' p
* D( C" O, ?( @// 归并两个大小为s的相邻段 4 p/ ~; v) T5 C0 r. e6 m$ I" A6 Y: S1 q6 e1 f. B
Merge(x, y, i, i+s-1, i+2*s-1);* c6 i0 i4 U" W7 ]+ K8 i
$ b9 j# D1 v, W* oi = i + 2 * s; 0 m3 d5 U+ a0 {3 b% G8 l8 T8 n% K 2 c! W+ @; t {! g: V} , G! H# x+ w' y/ O2 V o" u \/ [/ {+ K. n! q* G' R* O// 剩下不足2个元素 ; H. C0 h$ {& u5 H# z( `4 @1 C9 {% I7 B, F0 f" P2 C$ A+ X
if (i + s < n) Merge(x, y, i, i+s-1, n-1); - z5 q4 |: } f8 t, K, Q) |! V* I0 d& a; s
else for (int j = i; j <= n-1; j++)# s6 S# y! V( x7 }3 d
' c- p, U1 [" {2 D% d+ H// 把最后一段复制到y) H5 {# }$ F1 ^* O+ l) \
( C. r3 {; o# n* o7 T
y[j] = x[j];; K+ U. e- E* }1 g% ]( x
7 k3 Z+ N2 N1 T}0 b$ M7 V+ E, b9 E1 {+ m: `* Y
& [ l, @2 g4 D" q( M
程序14-5 Merge函数! z7 F$ M% }6 G
. k! C( x. t V( S2 Z2 \2 Y. mtemplate<CLASS T> 5 s) b {, r5 U2 E' \- b. Q$ R" Q, a E8 j, ~# V
void Merge(T c[], T d[], int l, int m, int r) i* x1 ^/ z/ Y4 @6 G3 q; ?, E! D. w2 z6 ~
{// 把c[l:m]] 和c[m:r] 归并到d [ l : r ] .% d3 o% [- D' c3 r- ~) \
# e2 G+ e, ]7 tint i = l, // 第一段的游标 4 h" M+ u& o- {6 n6 S * [0 P/ \- B* w2 f& aj = m+1, // 第二段的游标 U+ H) C* o' a c( K 0 X3 x( E* P8 S2 N }7 a/ Kk = l; // 结果的游标 4 {9 B! Q3 Y- W) o1 {2 H " _( j) [7 ~, b% _ N6 }/ /只要在段中存在i和j,则不断进行归并) Y% W& D( U2 u% a, T/ m
9 N2 h! l L d: fwhile ((i <= m) && (j <= r))4 _% m/ V+ A/ I* f y: Z
+ m7 A! }( t0 g2 N
if (c <= c[j]) d[k++] = c[i++]; : {4 `& s" v. }; N! {$ Z& Q2 a% L1 `# D- @$ A! N$ ^& Z
else d[k++] = c[j++];8 }$ H$ z6 B% O- M6 R. t
0 o! G2 w0 V/ \, F% G& Z/ v2 p9 j// 考虑余下的部分+ v+ c6 S" W3 s; N8 f, ^( i6 y: X
# y, r2 d2 L! |
if (i > m) for (int q = j; q <= r; q++)0 `# v! y; Q3 t* @" ~
2 {9 @# u5 a' |3 y' W0 Hd[k++] = c[q];8 b+ r( V- y. B( b: `9 _* ~