<>分而治之方法还可以用于实现另一种完全不同的排序方法,这种排序法称为快速排序(quick sort)。在这种方法中, n 个元素被分成三段(组):左段l e f t,右段r i g h t和中段m i d d l e。中段仅包含一个元素。左段中各元素都小于等于中段元素,右段中各元素都大于等于中段元素。因此l e f t和r i g h t中的元素可以独立排序,并且不必对l e f t和r i g h t的排序结果进行合并。m i d d l e中的元素被称为支点( p i v o t )。图1 4 - 9中给出了快速排序的伪代码。& V" T5 a. I, o6 Z5 r1 T
2 ~$ ^. U/ ~- x. {9 F$ ^; |6 S5 a- h: E5 d `
/ /使用快速排序方法对a[ 0 :n- 1 ]排序 % Q# _& c' N e! f" j8 e5 x% {, b/ X" d
从a[ 0 :n- 1 ]中选择一个元素作为m i d d l e,该元素为支点9 v" s9 \# q' z( C
; k4 g5 _; G0 G3 G7 y4 W* h
把余下的元素分割为两段left 和r i g h t,使得l e f t中的元素都小于等于支点,而right 中的元素都大于等于支点% i' v" s2 M G P/ Q
T+ {: Q! u/ `+ O8 ~! t递归地使用快速排序方法对left 进行排序 8 Q# m* w+ t" j: u/ N2 X1 {$ @# }
递归地使用快速排序方法对right 进行排序 + V, A: J5 b- R8 }! i' k z8 B# H6 X! Z8 ~所得结果为l e f t + m i d d l e + r i g h t ( P2 E5 _; }+ C& a3 I' x. g $ `* M4 [( L; Y- X图14-9 快速排序的伪代码 5 V2 I, v( E- R . N+ X V* V7 b 8 e( R0 [' d% K0 h考察元素序列[ 4 , 8 , 3 , 7 , 1 , 5 , 6 , 2 ]。假设选择元素6作为支点,则6位于m i d d l e;4,3,1,5,2位于l e f t;8,7位于r i g h t。当left 排好序后,所得结果为1,2,3,4,5;当r i g h t排好序后,所得结果为7,8。把right 中的元素放在支点元素之后, l e f t中的元素放在支点元素之前,即可得到最终的结果[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]。1 D( x- z8 A* U, n9 k D
4 X+ l* n& ~2 Z- G+ x# m9 m# a把元素序列划分为l e f t、m i d d l e和r i g h t可以就地进行(见程序1 4 - 6)。在程序1 4 - 6中,支点总是取位置1中的元素。也可以采用其他选择方式来提高排序性能,本章稍后部分将给出这样一种选择。/ U+ w: P1 ? p
$ a4 T: i( T9 J* @程序14-6 快速排序 4 a/ T r' x2 D2 t3 h3 Q& W9 t" Q5 D3 X" \
template<CLASS T> . S: R( q$ \/ t5 {6 O! K% J( F 8 i* j+ b6 r, rvoid QuickSort(T*a, int n) & n- O/ Q2 A! \1 \2 l$ |6 x! l1 k- B: V, p' N
{// 对a[0:n-1] 进行快速排序$ J" s0 w* I3 R& p
" H. W' l% u p. ]{// 要求a[n] 必需有最大关键值4 _' y, k. t3 e- P7 N' S
0 o" R, d& Y0 [; ?6 e/ I
quickSort(a, 0, n-1);. o) g& ]+ g& v T
$ r( W3 W( g+ t4 \( ^
template<CLASS T>' i! A6 ^4 x8 n; f, a+ g4 N
" _/ l9 e! u( D% o3 J
void quickSort(T a[], int l, int r) 1 j3 ]$ H9 a9 v# \3 I$ s7 |3 u0 R% i. a2 M9 Z
{// 排序a [ l : r ], a[r+1] 有大值 5 z- m3 l+ I2 ^ 2 ^% H( ?2 z5 Qif (l >= r) return; ) I) T; J7 i) S8 Y" f S9 q% }! i; d( C/ `9 _' ]
int i = l, // 从左至右的游标2 ?2 C4 m s9 m5 I( ]2 L% C
4 Q/ n" g) k% V0 X5 j& mj = r + 1; // 从右到左的游标 $ g: p7 h& i! a3 R$ w3 v- Z . X1 P; q# A7 q$ U3 ^T pivot = a[l]; / T2 ?' C: M o) e4 H8 D2 x6 Z0 N7 J7 ~. q
// 把左侧>= pivot的元素与右侧<= pivot 的元素进行交换 8 V) C: A' E; c7 R0 g , |5 v/ u/ l. r3 u0 dwhile (true) {3 m3 ?0 Y/ n0 U5 y4 S( U
8 e, K- d, X6 x _3 S5 y* E
do {// 在左侧寻找>= pivot 的元素 x6 z2 z" s4 L+ s- D
5 G8 J9 z. P7 D' h4 \) g% Z- Z( B5 mi = i + 1;* V! B6 @, T" O0 m& W
/ }: [5 ~3 ]2 l4 V- m: _
} while (a < pivot); 6 z: p2 I0 [( k4 j , t$ Y( i7 O& b( t! N9 b; qdo {// 在右侧寻找<= pivot 的元素- H% k% h7 i; W* S, J' v
: U1 E) L, g; V} * c. [$ M+ ] r. C7 K 8 b+ `/ f# }' B" u* q' h8 [// 设置p i v o t% w1 q- Q% b; j" o: D. B4 p
% |% W" ]' i$ D% \# V5 c: G
a[l] = a[j];+ [% p$ ]' V3 o
a' h$ |8 H) Q, a+ F; p8 ~a[j] = pivot; # N4 ]3 f( L* M& ~$ w- u- @/ d; z
quickSort(a, l, j-1); // 对左段排序 . s. _6 ?! b' e5 M6 a" L- j1 P7 t& T' E' S8 m5 p) N3 ]0 K0 H. y
quickSort(a, j+1, r); // 对右段排序 6 |4 T% @" k+ h 8 y5 E* ]/ I, z3 ]2 h4 R} ; w" r* p$ B0 }) q2 l" x : L* y2 Y8 y+ U @若把程序1 4 - 6中d o - w h i l e条件内的<号和>号分别修改为< =和> =,程序1 4 - 6仍然正确。实验结果表明使用程序1 4 - 6的快速排序代码可以得到比较好的平均性能。为了消除程序中的递归,必须引入堆栈。不过,消除最后一个递归调用不须使用堆栈。消除递归调用的工作留作练习(练习1 3)。程序1 4 - 6所需要的递归栈空间为O (n)。若使用堆栈来模拟递归,则可以把这个空间减少为O ( l o gn)。在模拟过程中,首先对left 和right 中较小者进行排序,把较大者的边界放入堆栈中。在最坏情况下l e f t总是为空,快速排序所需的计算时间为(n2 )。在最好情况下, l e f t和r i g h t中的元素数目大致相同,快速排序的复杂性为(nl o gn)。令人吃惊的是,快速排序的平均复杂性也是(nl o gn)。 & d. k" U7 P: |0 X* F* i. s( q% V( g) c3 \5 s S, Z6 K4 b" t
定理2-1 快速排序的平均复杂性为(nl o gn)。9 L4 X2 _0 A0 S
# t* C, r0 z3 S0 o2 t9 l0 o2 I' m$ ?证明用t (n) 代表对含有n 个元素的数组进行排序的平均时间。当n≤1时,t (n)≤d,d为某一常数。当n <1时,用s 表示左段所含元素的个数。由于在中段中有一个支点元素,因此右段中元素的个数为n-s- 1。所以左段和右段的平均排序时间分别为t (s), t (n-s- 1 )。分割数组中元素所需要的时间用cn 表示,其中c 是一个常数。因为s 有同等机会取0 ~n- 1中的任何一个值. 7 b3 Y2 U/ A* z$ K) B l+ E( m& |5 D* ]如对(2 - 8)式中的n 使用归纳法,可得到t (n)≤kn l o ge n,其中n> 1且k=2(c+d),e~2 . 7 1 8为自然对数的基底。在归纳开始时首先验证n= 2时公式的正确性。根据公式( 1 4 - 8),可以得到t( 2 )≤2c+ 2d≤k nl o ge 2。在归纳假设部分,假定t(n)≤kn l o ge n(当2≤n<m 时,m 是任意一个比2大的整数=.4 p1 ^: b" F. R* ]6 p
4 G, n# W, ^* x6 ~2 H8 oif(l >= r)r e t u r n ;: X( M! }- S0 u% C
H- ]% p2 u8 J
替换为 : @1 U# @2 L! h" j6 {8 _4 _! M 8 w2 S2 e) s6 r5 nif (r-1<NBREAK) {InsertionSort(a,l,r); return;} - q; T6 ~, t0 u. @4 S9 G1 | . i- Q4 Y6 e2 U1 B! D% Z这里I n s e r t i o n S o r t ( a , l , r )用来对a [ 1 : r ]进行插入排序。测量修改后的快速排序算法的性能留作练习(练习2 0)。用更小的值替换n B r e a k有可能使性能进一步提高(见练习2 0)。8 R2 l# s& X0 L$ M
% V! F; f& v" J1 {$ C
大多数实验表明,当n>c时(c为某一常数),在最坏情况下归并排序的性能也是最佳的。而当n≤c时,在最坏情况下插入排序的性能最佳。通过将插入排序与归并排序混合使用,可以提高归并排序的性能(练习2 1)。</P>