<>分而治之方法还可以用于实现另一种完全不同的排序方法,这种排序法称为快速排序(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中给出了快速排序的伪代码。 ; j6 N& G1 |3 V 1 a3 l3 E- X, a / M! S0 t! {( j& K/ /使用快速排序方法对a[ 0 :n- 1 ]排序; g- ^1 J/ N% `) I5 H) p
( D4 X+ V6 _) K' {$ x从a[ 0 :n- 1 ]中选择一个元素作为m i d d l e,该元素为支点 * ^7 V$ L4 \0 k 3 K2 T6 m# s* L: m/ B9 J把余下的元素分割为两段left 和r i g h t,使得l e f t中的元素都小于等于支点,而right 中的元素都大于等于支点, h$ K4 Z! f3 P0 H8 F8 S3 y
" Y, z1 U/ m9 S9 j# A! P递归地使用快速排序方法对left 进行排序 & h( i- u* _& r1 \ 9 ?0 w) Z; q9 }% L; k0 p递归地使用快速排序方法对right 进行排序 3 V9 y1 E2 o, _3 l6 U 1 a" l" I# T# S8 H8 k1 V所得结果为l e f t + m i d d l e + r i g h t . f! ^4 | U0 T% N( ?) Z0 d$ E' Z$ h- F t
图14-9 快速排序的伪代码 5 n f+ P+ p: U, L7 b( }' g# c ( r0 H4 c# c$ l/ J* j' }9 M0 w Z. y# ]- j
考察元素序列[ 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 ]。 $ F4 r/ u3 W0 ?- C& y: v' r- D+ T * x1 U4 ^: t. }0 v把元素序列划分为l e f t、m i d d l e和r i g h t可以就地进行(见程序1 4 - 6)。在程序1 4 - 6中,支点总是取位置1中的元素。也可以采用其他选择方式来提高排序性能,本章稍后部分将给出这样一种选择。 0 f+ H; b- i0 x& K' N$ L; r% ]: T# q* W1 d: u7 a- v v
程序14-6 快速排序 h6 k+ @$ n8 @
/ m; w5 q9 e$ X: I0 F( atemplate<CLASS T> ; m0 l% [! K* W- T- A8 ~: S3 x& w& X* p3 T( a: Q
void QuickSort(T*a, int n); w# d3 N+ m9 B# Z8 X( j5 T
4 e! z; I I& P7 H/ S- W' R2 U
{// 对a[0:n-1] 进行快速排序 9 g/ [5 b4 q0 @7 W8 b) s 1 r$ L, B" O! r{// 要求a[n] 必需有最大关键值 % R/ b; w$ C& ?8 |. I5 a! i: Q+ l1 C2 l$ _/ q
quickSort(a, 0, n-1); 6 y. `1 S% T+ d& v% A! R- P9 R6 ^2 b3 @* F! X7 ]! G* K
template<CLASS T> 2 N& D. U& V* @ ?% i* T0 E" A6 B' M ]$ v- i1 H
void quickSort(T a[], int l, int r) 9 r9 w9 o9 g' T: J7 s6 p1 t7 c4 b' ? n. h" y+ R" z( f. T
{// 排序a [ l : r ], a[r+1] 有大值# L. |9 J0 H. b: Y0 X
: Y l, u& u( I3 y& x
if (l >= r) return; 7 {% W3 ~2 G3 ~' r* m( e* @; k1 i( W% l$ \6 Q G
int i = l, // 从左至右的游标 8 n& S; h! e, j" @4 ? J' p+ w3 O/ t) M
j = r + 1; // 从右到左的游标9 L5 C) L0 _$ K# V/ J. g# E
0 F& d- U$ V' b D n" D* z0 J冒泡排序n2 n28 g* E' I p5 X) [, ]# b0 A1 D6 P
/ O1 w) E- D2 i3 e
计数排序n2 n2! g% n& q# M/ ?$ S( C* f
& Q v* h. G8 m0 W1 A2 N% k Y
插入排序n2 n2( h& ]" E) Q) G# f+ |
" L' Q: U+ J. ^- Z* L选择排序n2 n2: N- N1 X% W l( J* ?
" O' n6 G+ s {. @* F+ s7 t
堆排序nl o gn nl o gn% B& M+ b @: q5 j1 P
) L$ Y+ }5 I3 R% I
归并排序nl o gn nl o gn 0 @' P4 k! w6 r! N. g) @ j l9 d9 [. t! m
快速排序n2 nl o gn7 w1 z" o2 e8 J6 X
$ b! Y$ {5 d7 {' p5 a图14-10 各种排序算法的比较 * r5 N( S: V- b1 x' @' ?! n* v 8 g6 {: h0 C; z1 k* t" H4 b* L$ G8 |. [) f+ ?
中值快速排序( median-of-three quick sort)是程序1 4 - 6的一种变化,这种算法有更好的平均性能。注意到在程序1 4 - 6中总是选择a [ 1 ]做为支点,而在这种快速排序算法中,可以不必使用a [ 1 ]做为支点,而是取{a[1],a[(1+r)/2],a[r]} 中大小居中的那个元素作为支点。例如,假如有三个元素,大小分别为5,9,7,那么取7为支点。为了实现中值快速排序算法,一种最简单的方式就是首先选出中值元素并与a[1] 进行交换,然后利用程序1 4 - 6完成排序。如果a [ r ]是被选出的中值元素,那么将a[1] 与a[r] 进行交换,然后将a [ 1 ](即原来的a [ r ])赋值给程序1 4 - 6中的变量p i v o t,之后继续执行程序1 4 - 6中的其余代码。 5 f" }6 {- U5 \9 u$ ?! r) J8 E/ D" f! X- j O' ]$ r
图2 - 11中分别给出了根据实验所得到的归并排序、堆排序、插入排序、快速排序的平均时间。对于每一个不同的n, 都随机产生了至少1 0 0组整数。随机整数的产生是通过反复调用s t d l i b . h库中的r a n d o m函数来实现的。如果对一组整数进行排序的时间少于1 0个时钟滴答,则继续对其他组整数进行排序,直到所用的时间不低于1 0个时钟滴答。在图2 - 11中的数据包含产生随机整数的时间。对于每一个n,在各种排序法中用于产生随机整数及其他开销的时间是相同的。因此,图2 - 11中的数据对于比较各种排序算法是很有用的。 # ~2 ]2 C& H; m! T" ^- X6 k! _5 n& R
对于足够大的n,快速排序算法要比其他算法效率更高。从图中可以看到快速排序曲线与插入排序曲线的交点横坐标比2 0略小,可通过实验来确定这个交点横坐标的精确值。可以分别用n = 1 5 , 1 6 , 1 7 , 1 8 , 1 9进行实验,以寻找精确的交点。令精确的交点横坐标为nBr e a k。当n≤nBreak 时,插入排序的平均性能最佳。当n>nBreak 时,快速排序性能最佳。当n>nBreak 时,把插入排序与快速排序组合为一个排序函数,可以提高快速排序的性能,实现方法是把程序1 4 - 6中的以下语句: $ a- a, K% @# }" y$ P8 z% o6 S ^, \5 b, f! P" b
if(l >= r)r e t u r n ; 0 h: `& w1 m- \+ a+ G( k9 z2 n, ^" z/ a; b
替换为 ) @" R$ T. s1 M" l2 M8 W( E& g2 z
if (r-1<NBREAK) {InsertionSort(a,l,r); return;} 4 m1 T$ Q+ T, @& i0 Y3 ^1 E5 ]7 Q . m8 Z3 \0 ^9 ? V, @6 N. h' s4 H这里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)。, j ?% A& ~3 E9 D. @+ x! J1 I2 Z
& U4 i8 P" r! S/ O, W6 @' g
大多数实验表明,当n>c时(c为某一常数),在最坏情况下归并排序的性能也是最佳的。而当n≤c时,在最坏情况下插入排序的性能最佳。通过将插入排序与归并排序混合使用,可以提高归并排序的性能(练习2 1)。</P>