<>分而治之方法还可以用于实现另一种完全不同的排序方法,这种排序法称为快速排序(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中给出了快速排序的伪代码。! H/ @7 h7 I: u! A) q
' t" g6 n9 A: E+ V6 I$ F6 |% |4 v# T* c& z1 _
/ /使用快速排序方法对a[ 0 :n- 1 ]排序 9 R# c! k1 l. b$ F# L. t2 f, \0 F6 e+ b1 G+ |
从a[ 0 :n- 1 ]中选择一个元素作为m i d d l e,该元素为支点2 r+ e/ _8 d: E" T7 U3 b
# Y( y& Q) L; B6 e0 O6 _
把余下的元素分割为两段left 和r i g h t,使得l e f t中的元素都小于等于支点,而right 中的元素都大于等于支点; L4 r* a9 [ y$ `' p6 T% h
( i# d. z- q$ K* P. Y' R! O* [2 x
递归地使用快速排序方法对left 进行排序 ) C$ D+ B9 [, V9 y; j 0 d, M" G/ L; j/ H& F递归地使用快速排序方法对right 进行排序 . t4 ~+ o. a7 t! U2 O, B9 `( }4 z" n, a7 l) o% k6 n2 u6 o* D
所得结果为l e f t + m i d d l e + r i g h t4 ~( Z3 M* P$ M6 {0 O7 x, h( D' V, |
* O! }; `& s* n! M! i- U图14-9 快速排序的伪代码 2 s: z% }8 i5 {4 m4 t6 [! C) O O) N2 {! E- V
: w# L- q& ^0 Y1 w4 n" A* X$ w
考察元素序列[ 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 S+ f1 |3 K/ r4 @4 n( j6 T " G' r* U1 y% J9 {: L8 g. D) R% M/ t把元素序列划分为l e f t、m i d d l e和r i g h t可以就地进行(见程序1 4 - 6)。在程序1 4 - 6中,支点总是取位置1中的元素。也可以采用其他选择方式来提高排序性能,本章稍后部分将给出这样一种选择。 . t5 s+ m5 G* s% M ) {$ _8 A6 x5 Q( C+ l5 K程序14-6 快速排序 " z6 E4 m6 f% x, M ' E. I( U+ m! m1 Y4 {/ I- o. ftemplate<CLASS T> 4 Y2 D5 g' ^& r5 Y+ a( n* O b 4 a$ r, z6 c0 i; J9 e6 g& jvoid QuickSort(T*a, int n)6 C+ ~( F' Y/ q Z: Y7 y6 `
! U4 \0 n$ C2 X* a! \
{// 对a[0:n-1] 进行快速排序; d( f/ ?. P* e& V
* m2 B& g# h; o' V. }! G{// 要求a[n] 必需有最大关键值 $ g( a( A# [" {9 H : o# D8 [' k/ }' HquickSort(a, 0, n-1); # ?- e( U6 H0 a+ Z6 {3 Q9 S$ M9 p {# c* ?; m' o) l. X) ~" S" O/ }8 Etemplate<CLASS T> ! K# T- |& V( x N( a5 D( }9 P. P; X& t {! s# J! r. ?
void quickSort(T a[], int l, int r) * f- w$ s" I( @% n' N9 [3 B+ Z ! j2 @. ]" v/ P! `+ i6 Z{// 排序a [ l : r ], a[r+1] 有大值3 T; i7 O0 I# S( U2 O* m8 c
5 _ C9 q+ N' P4 {if (l >= r) return;3 O& p; Y2 a" W& ~7 `' M e
& ~6 o5 d2 C0 n5 S
int i = l, // 从左至右的游标9 x: W; B; b v* l' J
$ N P& {$ C5 S/ k. bj = r + 1; // 从右到左的游标 ' h: N4 [4 x" E$ x $ t0 w- r7 K# P( e- ET pivot = a[l];1 p ]# F, v1 { v3 X, s
: e' G. k$ Z8 O) n- m3 u9 x
// 把左侧>= pivot的元素与右侧<= pivot 的元素进行交换 H- x6 s8 q9 z" E+ A6 A
6 L) d$ l) D6 B8 {6 u9 ^while (true) {2 ~7 H0 z- U3 f' g
; q t4 b! [( C5 n( R+ f* vdo {// 在左侧寻找>= pivot 的元素! V- v4 w1 m- n, s; s1 n, a
3 }# ?& I" Z0 |) e) o. n* q# k
i = i + 1; 3 O' \: o$ Y8 m/ K 7 a6 V3 x, [! o& a& r" k} while (a < pivot);$ o' B& x" C/ [- A$ H
# K% R/ i( [9 K1 ~// 设置p i v o t) t- N. f0 n( o! ]+ G
" `2 t. t; Z4 S* Z3 v, j1 k
a[l] = a[j]; s" t3 Q" V1 P9 U! ^" _ " x* p% E% R6 ^" Ua[j] = pivot; $ N0 J, ?- H/ f" b* w% J8 C I; s/ p
quickSort(a, l, j-1); // 对左段排序9 h$ M1 z5 B" w8 ]
# F* q" a( }/ r" i. EquickSort(a, j+1, r); // 对右段排序 + n4 W% L0 v" `4 Y - B! W4 F8 s* @+ s}; D. @& e' K5 c+ _6 G
/ k. ^* J# t' L& H若把程序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)。7 E) x' c- e N
2 d6 p1 g: w4 ?if(l >= r)r e t u r n ; G5 `- [0 p9 S
4 i: Y: u* o4 s J' M
替换为8 L2 m9 W: v& z; V: K8 }2 `
+ z( Y" k7 {' M6 H( M1 O: ^: @+ B
if (r-1<NBREAK) {InsertionSort(a,l,r); return;}" q0 r+ P4 [, Q% Z% b+ P
) B+ y! R0 h* t& ^) i, v& M
这里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)。 9 z' Z/ d0 o) Q- h- @6 S1 [; [3 Y4 Z- j+ Q3 r# ^ v; k
大多数实验表明,当n>c时(c为某一常数),在最坏情况下归并排序的性能也是最佳的。而当n≤c时,在最坏情况下插入排序的性能最佳。通过将插入排序与归并排序混合使用,可以提高归并排序的性能(练习2 1)。</P>