- 在线时间
- 0 小时
- 最后登录
- 2007-9-23
- 注册时间
- 2004-9-10
- 听众数
- 3
- 收听数
- 0
- 能力
- 0 分
- 体力
- 9975 点
- 威望
- 7 点
- 阅读权限
- 150
- 积分
- 4048
- 相册
- 0
- 日志
- 0
- 记录
- 0
- 帖子
- 1893
- 主题
- 823
- 精华
- 2
- 分享
- 0
- 好友
- 0

我的地盘我做主
该用户从未签到
 |
< >可以运用分而治之方法来解决排序问题,该问题是将n 个元素排成非递减顺序。分而治之方法通常用以下的步骤来进行排序算法:若n 为1,算法终止;否则,将这一元素集合分割成两个或更多个子集合,对每一个子集合分别排序,然后将排好序的子集合归并为一个集合。
& J8 L3 L. W* j7 Q" \0 w
( O; t6 J/ I( z9 o8 x' H假设仅将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)的递归算法。
- ?) c! G1 h' {& }1 `: v: m4 C- X7 n$ E; c& M# U% c
假如用冒泡过程(见程序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)。! m5 k& _+ U7 B
) i3 x+ Y! Y: m& b- Q) I
上述分割方案将n 个元素分成两个极不平衡的集合A和B。A有n- 1个元素,而B仅含一个元素。下面来看一看采用平衡分割法会发生什么情况: A集合中含有n/k 个元素,B中包含其余的元素。递归地使用分而治之方法对A和B进行排序。然后采用一个被称之为归并( m e rg e)的过程,将已排好序的A和B合并成一个集合。
5 P* i8 H. q+ e/ M8 ^5 E8 \6 o/ @4 {# b4 g {
例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 ]。当这两个排好序的序列被归并后,即可得所需要的排序序列。
" B+ Q H# ^) n7 [/ d. [7 ^
9 _2 H& C( b' d) B图2 - 6给出了分而治之排序算法的伪代码。算法中子集合的数目为2,A中含有n/k个元素。
0 ?% t0 o; |/ q0 o0 i9 q7 x& R5 [. A1 f' u; f. W$ Z
template<CLASS T>+ T8 x X# o! D5 \* N! w" I5 d- J3 k
+ z: B' J Z/ q* _! K
void sort( T E, int n)8 O# {0 ~ J1 o3 J
$ \: N$ m% ]8 d5 I$ e# d
{ / /对E中的n 个元素进行排序, k为全局变量% B4 h8 R9 I* g! `
. K# T3 k$ U' g6 S9 h3 eif (n >= k) {
. L, ?* e# _, q% f
7 d6 ^- A h. fi = n/k;
9 z) ?; J/ N, ? S' ?
; A, q4 A* y( |j = n-i;- I. t, I" ?5 i5 U1 B
" ^8 ]( `5 n- O令A 包含E中的前i 个元素2 |* }+ x2 ?& G( U3 d7 h4 A8 i( z
, b6 |* t! T* i H令B 包含E中余下的j 个元素
7 D' `' N1 k* i8 K# V! @& j7 |) K5 l( `
s o r t ( A , i ) ;/ L/ t0 O. ~% K, a6 {$ g V
: w4 @- e# Y1 V6 T6 E2 B& ~s o r t ( B , j ) ;
- E7 |9 Y S, g' y# [. U B" K7 P' \6 G
m e rge(A,B,E,i,j,); //把A 和B 合并到E- ?$ n) }+ P7 r* z/ v1 I' `
% H8 i2 B3 E4 b g2 h}
O( l* a* W% U: N6 q+ S$ k- E* c7 j2 C/ i3 h
else 使用插入排序算法对E 进行排序- S! F* p8 D1 ? t
" e* Y# f& U+ f6 _7 s+ p! Q) b}4 E( B+ e+ U/ n6 X
6 c- \* X0 f- w4 a# D
图14-6 分而治之排序算法的伪代码
: v8 i- k( n7 H9 y
& c/ G( T, B' T, j+ D& S' T/ b, U/ z
: }- u5 X! [. D, A, d
从对归并过程的简略描述中,可以明显地看出归并n个元素所需要的时间为O (n)。设t (n)为分而治之排序算法(如图1 4 - 6所示)在最坏情况下所需花费的时间,则有以下递推公式:' S' T5 h5 U/ F2 n4 b& |+ a1 H2 Y
& q- ]- k. i- M其中c 和d 为常数。当n / k≈n-n / k 时,t (n) 的值最小。因此当k= 2时,也就是说,当两个子集合所包含的元素个数近似相等时, t (n) 最小,即当所划分的子集合大小接近时,分而治之算法通常具有最佳性能。+ V o- h# K3 H# T
/ \- L- K0 E2 n( O$ s+ o5 e可以用迭代方法来计算这一递推方式,结果为t(n)= (nl o gn)。虽然这个结果是在n为2的幂时得到的,但对于所有的n,这一结果也是有效的,因为t(n) 是n 的非递减函数。t(n) =(nl o gn) 给出了归并排序的最好和最坏情况下的复杂性。由于最好和最坏情况下的复杂性是一样的,因此归并排序的平均复杂性为t (n)= (nl o gn)。* |/ \- E w2 b/ g
M2 U+ ]3 ?6 _; J$ h图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分成两个大致相等的链表。9 A8 s2 H( _" x" g4 x1 J/ u/ [
& ]8 b! F* {5 x0 f) {6 t$ u3 c
归并过程应能将两个已排序的链表归并在一起。如果希望把所得到C + +程序与堆排序和插入排序进行性能比较,那么就不能使用链表来实现归并排序,因为后两种排序方法中都没有使用链表。为了能与前面讨论过的排序函数作比较,归并排序函数必须用一个数组a来存储元素集合E,并在a 中返回排序后的元素序列。为此按照下述过程来对图1 4 - 6的伪代码进行细化:当集合E被化分成两个子集合时,可以不必把两个子集合的元素分别复制到A和B中,只需简单地在集合E中保持两个子集合的左右边界即可。接下来对a 中的初始序列进行排序,并将所得到的排序序列归并到一个新数组b中,最后将它们复制到a 中。图1 4 - 6的改进版见图1 4 - 7。
7 N8 \/ i/ q* |. d& j9 K# J
S/ c9 D' v* ~$ I' k! C) O) n+ z! F Q& h% _) k6 W8 g
9 t# }8 ~8 e+ n: A+ ctemplate<CLASS T>
h% m& G) b+ k3 ?2 }- P# Z8 Z
6 ]% p1 d9 g3 h$ b- J5 m! i! b$ dM e rgeSort( T a[], int left, int right)" J& W3 F$ ]3 g! C: i
& S, v9 f5 [: e$ ]7 s4 ~" L0 j{ / /对a [ l e f t : r i g h t ]中的元素进行排序3 X% n5 f$ o# m' U4 K! N0 V; Z
5 s5 A v. P' p* m6 F4 [if (left < right) {//至少两个元素1 u2 S" s3 u& i$ q
K) D0 X: g$ x0 m
int i = (left + right)/2; //中心位置
+ N; F6 }( O; l- o2 A6 U
/ s6 X3 N3 V8 S3 gM e rgeSort(a, left, i);
- Y: W2 q, i7 D: r2 I3 T) t$ [' `' V. `% { s$ X
M e rgeSort(a, i+1, right);& D, j( f/ u9 D3 F6 @, u
g+ V; V' b4 w" E, q
M e rge(a, b, left, i, right); //从a 合并到b
& Z( Q: p6 r" Y8 ]3 G8 _7 B
* g* Y v+ ^- k8 u! r% s! i1 c2 qCopy(b, a, left, right); //结果放回a
1 a- a" T% A @% L% [9 B, x% t8 S3 J' K. v
}
' R% M+ c% v! Z! N/ ^; j: u
1 |8 p! ]5 i$ b4 m2 \}+ }7 n. d8 \; F- u+ c: g J
& U4 f* g! _& n. a4 y' k( }0 W
图14-7 分而治之排序算法的改进, w4 {* p8 w. L$ i5 {4 h8 h9 U5 g) f
/ @2 R9 ^1 a9 [1 y4 H( y
, q8 W7 ^6 [5 j8 L* Y2 w% o" Y
5 T* k8 W- D5 \
可以从很多方面来改进图1 4 - 7的性能,例如,可以容易地消除递归。如果仔细地检查图1 4 - 7中的程序,就会发现其中的递归只是简单地重复分割元素序列,直到序列的长度变成1为止。当序列的长度变为1时即可进行归并操作,这个过程可以用n 为2的幂来很好地描述。长度为1的序列被归并为长度为2的有序序列;长度为2的序列接着被归并为长度为4的有序序列;这个过程不断地重复直到归并为长度为n 的序列。图1 4 - 8给出n= 8时的归并(和复制)过程,方括号表示一个已排序序列的首和尾。
' |# h8 G0 V/ H# U9 W3 A- u
3 ~+ I% ^. B9 B$ A1 R6 T8 |$ x. i5 y! ~* p
3 `# O( ?- D0 `初始序列[8] [4] [5] [6] [2] [1] [7] [3]
: u' N5 u( q3 A. o; u# g* `
& E! {: e9 B. E8 Y" }. }7 E- u归并到b [4 8] [5 6] [1 2] [3 7]0 x8 P% W4 S( L$ m5 o5 s
7 b' K1 ?4 {1 i2 L, _3 q1 @# J
复制到a [4 8] [5 6] [1 2] [3 7]
: o# E2 F/ O: i8 U3 i/ v6 p9 t7 N0 ^- X' \. C7 x
归并到b [4 5 6 8] [1 2 3 7]& D2 H% ?2 Y' n8 k' [
- y$ o3 w$ F1 J# F: e9 n/ _" A复制到a [4 5 6 8] [1 2 3 7]& E, j, a% I4 r: P, {4 \: U- E8 X! e+ m
. [, V+ j [) q+ x, l+ N归并到b [1 2 3 4 5 6 7 8]
# G: g6 _* T7 {# l I% {" W
5 `( G2 | D5 L o复制到a [1 2 3 4 5 6 7 8]/ B' h5 V, `( _# {8 {
) l, F# t' T* F5 i6 B图14-8 归并排序的例子
$ H9 Z* r+ u6 f3 e, S) Y
2 j0 k5 {! |' J; k& i7 L8 D+ O( E& I6 S' @7 H7 W+ m1 C5 b5 \7 p
/ L3 F! p2 c0 R7 F! O# M) e, ^另一种二路归并排序算法是这样的:首先将每两个相邻的大小为1的子序列归并,然后对上一次归并所得到的大小为2的子序列进行相邻归并,如此反复,直至最后归并到一个序列,归并过程完成。通过轮流地将元素从a 归并到b 并从b 归并到a,可以虚拟地消除复制过程。二路归并排序算法见程序1 4 - 3。
' H y+ L! [* E% \. K- I1 b- ^0 f. a# ]5 w" F, q, j& P! a
程序14-3 二路归并排序: P& j" Y/ c) V5 ~# O! H& N
# o" D) }4 Z6 r6 X: ^4 M
template<CLASS T>/ I& s# Y/ M- n! m0 w1 f3 t* X
+ O( r2 T& o& t
void MergeSort(T a[], int n)
/ R- |1 F+ W' Q8 f; g. \
* _7 Q2 t/ q. S% ]{// 使用归并排序算法对a[0:n-1] 进行排序" e- `, a6 s" k: ~1 h, u: g/ H
2 R$ d: C) _9 O6 }6 p/ ~3 A
T *b = new T [n];; f6 O: G* D; M2 O. Y
5 r5 s8 n$ [8 Y5 H8 Z/ T
int s = 1; // 段的大小6 Y# U& D' M; K( N5 [) m8 e& |! C
5 w! I& F/ h* o
while (s < n) {
9 N7 W/ y8 ?( i! n- {/ w% t
2 u4 ^ Q A# Y# VMergePass(a, b, s, n); // 从a归并到b
" h' C+ L; Z) n9 [7 a$ b1 f& u( M* d/ T: G
s += s;
4 D/ [; w6 S) s
( q6 g* @ }6 \% G( SMergePass(b, a, s, n); // 从b 归并到a, I6 Y: X% @3 L
, N1 O6 ]# Z" X8 L; ^* ^
s += s;% {( S! X% `- L+ t, X( L5 `# _
, V/ G+ R, x* [$ j1 [: z" q1 b5 C}
, H9 Y4 X% w9 ` R7 R9 E6 ?* t$ |& r' O: h, D9 m( J
}
+ N) a8 o0 j7 s: h& H0 d: F; M' }# M1 O4 _% Z9 ~1 C
为了完成排序代码,首先需要完成函数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定义一个操作符< =。如果需要排序的数据类型是用户自定义类型,则必须重载操作符< =。这种设计方法允许我们按元素的任一个域进行排序。重载操作符< =的目的是用来比较需要排序的域。- @0 i# J% G- o7 F) y. I# _1 [: T- x
8 Q; E8 t2 Y! e; N# K# Q程序14-4 MergePass函数
5 q: }6 [9 h( O5 L/ L4 q
( H2 }1 R2 Y0 i- ?" Ztemplate<CLASS T>
/ N& h5 n/ M' W1 Z$ f! Q+ J1 Y
, Q, E, B8 f* l2 q: y5 H+ Ovoid MergePass(T x[], T y[], int s, int n), |$ }& J5 s7 G# \
' @9 K2 s+ s) w( R# @6 j% w{// 归并大小为s的相邻段+ r$ _* h% Q1 r3 D& E7 {
2 u+ t/ v+ V1 y0 z0 l
int i = 0;
8 G. E' @3 S, O
4 G7 c. d. [; S8 u0 ~. H/ Pwhile (i <= n - 2 * s) {/ t& C- l& i4 T4 E6 g% Q
. I' e% k7 |' K- w c+ S2 g
// 归并两个大小为s的相邻段! X y( P5 d- n8 A# b
- t% ?% F2 v/ |: R2 }: `Merge(x, y, i, i+s-1, i+2*s-1);! N; {3 L' n3 o! z4 x$ v
6 \4 Z q7 v i3 f0 P. Ii = i + 2 * s;
8 m4 P- @" z+ H; b- B( F9 f4 O% r5 @" S$ e7 m# \
}) d( M6 Y1 p. t
' p' A: e4 C& E- H T// 剩下不足2个元素1 j# E/ S4 w! _- g5 w) V
* s3 f. I/ W6 o* B8 p
if (i + s < n) Merge(x, y, i, i+s-1, n-1);: P) n/ z5 B/ G7 ~1 f- A
/ t8 m4 l6 \/ ]1 a+ t: ^) R+ pelse for (int j = i; j <= n-1; j++)
; i" O( P; R( R% R- y* e' s# i* e; t
// 把最后一段复制到y
& n, r: y1 i2 e% `( @
; o* D, Z0 [/ Gy[j] = x[j];
/ [9 ^* b" \$ z) A
3 x5 j% O C5 l; q3 S/ y- i}" P0 x* d( r; n3 {+ u3 B; T. `
8 H$ L7 B! Q& e$ \' L3 q, q! q
程序14-5 Merge函数% p2 z2 X( F9 j# w
% [% E, [8 O1 |! E @: j
template<CLASS T>
- t( r. t* V) V" g% L, X# v+ S- N t1 \% A6 R' h
void Merge(T c[], T d[], int l, int m, int r). Y& x" g1 J& }; s; ^* }, a5 K6 F$ i
' H, r5 S7 Z8 `
{// 把c[l:m]] 和c[m:r] 归并到d [ l : r ] .
! H" H& p3 s; H: o' g7 f3 X6 j
* k9 A% R( ^$ W9 } A7 Cint i = l, // 第一段的游标
" ^3 c6 }6 d- {4 p) E0 l i. {
j = m+1, // 第二段的游标
, j$ ?# l+ {( e5 |. \ @
4 j. Y: m2 ^, K" f) fk = l; // 结果的游标
# I& B$ V' x: N. U( m
+ S8 Z: |% q1 m9 Y+ R( C1 z/ /只要在段中存在i和j,则不断进行归并
9 I7 d* [7 G( R5 j6 t. V8 Y/ p6 S0 b
while ((i <= m) && (j <= r))9 D% A- B [5 f5 z$ m0 @' o
! u2 J( |/ a( u5 N* c* H$ oif (c <= c[j]) d[k++] = c[i++];
/ L- Z$ J0 ~- h; k0 y
- u9 n9 g2 i" D9 @8 i2 helse d[k++] = c[j++];
$ D% P! D8 r- R/ f
7 c0 P7 T9 _+ v) I' u2 j, D' ?- C// 考虑余下的部分
5 x7 Y5 l: `# n# G: b& z+ O: G6 j7 B) f; n4 s
if (i > m) for (int q = j; q <= r; q++)% {' m# E" G+ O) w2 c
: ]# t9 u% k* E3 z6 [# H+ y2 T
d[k++] = c[q];
8 `/ Y4 B% `+ v4 { Z l( D- {+ ^2 m( `
else for (int q = i; q <= m; q++)
2 U B* L9 ]3 S, v
9 w1 s3 g0 c; b8 U$ |. Ed[k++] = c[q];1 i# \: h5 t, V3 r8 F
* @& J% o& g( j/ l$ B; {- Q3 K7 ~}5 ~/ j1 y: Z5 q7 b8 Q0 E
/ x$ N' r: }$ j自然归并排序(natural merge sort)是基本归并排序(见程序1 4 - 3)的一种变化。它首先对输入序列中已经存在的有序子序列进行归并。例如,元素序列[ 4,8,3,7,1,5,6,2 ]中包含有序的子序列[ 4,8 ],[ 3,7 ],[ 1,5,6 ]和[ 2 ],这些子序列是按从左至右的顺序对元素表进行扫描而产生的,若位置i 的元素比位置i+ 1的元素大,则从位置i 进行分割。对于上面这个元素序列,可找到四个子序列,子序列1和子序列2归并可得[ 3 , 4 , 7 , 8 ],子序列3和子序列4归并可得[ 1 , 2 , 5 , 6 ],最后,归并这两个子序列得到[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]。因此,对于上述元素序列,仅仅使用了两趟归并,而程序1 4 - 3从大小为1的子序列开始,需使用三趟归并。作为一个极端的例子,假设输入的元素序列已经排好序并有n个元素,自然归并排序法将准确地识别该序列不必进行归并排序,但程序1 4 - 3仍需要进行[ l o g2 n] 趟归并。因此自然归并排序将在(n) 的时间内完成排序。而程序1 4 - 3将花费(n l o gn) 的时间。</P> |
zan
|