数学建模社区-数学中国

标题: 选择排序 [打印本页]

作者: 韩冰    时间: 2004-10-4 05:17
标题: 选择排序
<>对于给定的n 个元素的数组a [ 0 : n - 1 ],要求从中找出第k小的元素。当a [ 0 : n - 1 ]被排序时,该元素就是a [ k - 1 ]。假设n = 8,每个元素有两个域k e y和I D,其中k e y是一个整数,I D是一个字符。假设这8个元素为[ ( 1 2 ,a),( 4 ,b),( 5 ,c),( 4 ,d),( 5 ,e),( 1 0 ,f),( 2 ,g),( 2 0 ,h)], 排序后得到数组[ ( 2 ,g),( 4 ,d),( 4 ,b),( 5 ,c),( 5 ,e),( 1 0 ,f),( 1 2 ,a),( 2 0 ,h) ]。如果k = 1,返回I D为g 的元素;如果k = 8,返回I D为h 的元素;如果k = 6,返回是I D为f 的元素;如果k = 2,返回I D为d 的元素。实际上,对最后一种情况,所得到的结果可能不唯一,因为排序过程中既可能将I D为d 的元素排在a [ 1 ],也可能将I D为b 的元素排在a [ 1 ],原因是它们具有相同大小的k e y,因而两个元素中的任何一个都有可能被返回。但是无论如何,如果一个元素在k = 2时被返回,另一个就必须在k = 3时被返回。9 w3 d5 O; r1 F" O
2 @& _* N4 ?/ ~/ i0 y# f( W3 R, d* v
选择问题的一个应用就是寻找中值元素,此时k = [n / 2 ]。中值是一个很有用的统计量,例如中间工资,中间年龄,中间重量。其他k值也是有用的。例如,通过寻找第n / 4 , n / 2和3 n / 4这三个元素,可将人口划分为4份。6 _2 @$ f- P1 Z. F; z! T
5 S& l9 S; w4 e* U1 l
选择问题可在O ( n l o g n )时间内解决,方法是首先对这n个元素进行排序(如使用堆排序式或归并排序),然后取出a [ k - 1 ]中的元素。若使用快速排序(如图1 4 - 11所示),可以获得更好的平均性能,尽管该算法有一个比较差的渐近复杂性O( n2 )。: w2 g$ L' @. i' Z4 A( l+ A

3 x4 |+ h& Z8 ~% d* l可以通过修写程序1 4 - 6来解决选择问题。如果在执行两个w h i l e循环后支点元素a [ l ]被交换到a [ j ] ,那么a [ l ]是a [ l : j ]中的第j - l + 1个元素。如果要寻找的第k 个元素在a [ l : r ]中,并且j - l + 1等于k,则答案就是a [ l ];如果j - l + 1 &lt; k,那么寻找的元素是r i g h t中的第k - j + l - 1个元素,否则要寻找的元素是left 中的第k个元素。因此,只需进行0次或1次递归调用。新代码见程序1 4 - 7。S e l e c t中的递归调用可用f o r或w h i l e循环来替代(练习2 5)。
+ [) n- S0 }  Q8 v) W# q) x6 w" H9 I; _/ o& y% _
程序14-7 寻找第k 个元素
" _& u! W6 s7 {* |4 V) x
% G0 ^4 N, v3 B# F8 [0 _1 s9 i/ Dtemplate<CLASS T>
  o( y- ]5 @" o/ ]
! W) z! Z( [( m. o$ Q& wT Select(T a[], int n, int k)
' X. }& ^+ e4 e0 c
: O3 v2 q! x- {0 j  k{// 返回a [ 0 : n - 1 ]中第k小的元素+ z) A# T& V  a
. y: t0 \. T6 T# e4 m2 T) U
// 假定a[n] 是一个伪最大元素
# Y& w& H9 I; _; k0 u# N* ^, A! E1 W3 U3 y8 B( U4 P
if (k &lt; 1 || k &gt; n) throw OutOfBounds();
/ O4 z  u& n. }, v. U3 Z
6 e/ I' O. v' b. }% Greturn select(a, 0, n-1, k);
$ p9 J3 M3 C0 H4 ?& t- j
- b' r% g+ }4 E3 F# G# f}+ J) ~$ I- V# Z& Q9 [& Q1 S
! Y1 P$ f8 s, A- ~4 n2 R
template<CLASS T>$ q& z. D6 X3 y+ q: E
6 h1 f6 [7 ]; }, u
T select(T a[], int l, int r, int k)
6 |% L& C, e$ M; b, a4 N
- o2 J( T! X' V% n9 u6 ^' j{// 在a [ l : r ]中选择第k小的元素! A/ F( [% t5 {6 U. u1 F

! C3 ^4 [  `2 O/ G$ }9 O" Aif (l &gt;= r) return a[l];
& }8 s) C0 _9 u5 B) q# g
9 a9 ]: q+ n* F: w( C: vint i = l, // 从左至右的游标! d; q0 {" V# r  U; s7 J! y! N

; e) D4 @2 ~" m( L- F0 }j = r + 1; // 从右到左的游标
* W. ?; F  C, z1 f6 x% \7 v* Z3 g/ p2 B4 G
T pivot = a[l];4 g% s, ]* G: K& ?" V
4 r) p4 E! S: [( y- A
// 把左侧&gt;= pivot的元素与右侧&lt;= pivot 的元素进行交换
5 e# i6 \9 ]! R$ O: O; `5 z
! d! j# W2 {! ?4 W4 e9 c5 Cwhile (true) {9 K  ?+ f5 k, P, l0 q9 X
, j7 g! c) A5 K
do {// 在左侧寻找&gt;= pivot 的元素
+ h, o+ u. \0 ^3 w) Y- b4 x
3 ~1 ~+ o$ i9 A, j% e/ Ei = i + 1;
8 C& T; J& W* K. B
! ~) m6 Y, p4 n* m' p% m9 B. [3 z  N} while (a &lt; pivot);) T6 V' s, t" R0 ]
* k, s1 `- Y3 D+ A1 ^
do {// 在右侧寻找&lt;= pivot 的元素5 z2 O9 n* A( ?  L2 V

; w0 O" T% l9 @3 lj = j - 1;9 P5 J  @6 ~& l* G
9 e0 Z9 v+ [- Q6 m
} while (a[j] &gt; pivot);+ c4 W* [; _5 H8 c0 F. f
6 V5 \5 ]2 z7 U$ q/ M; E9 k
if (i &gt;= j) break; // 未发现交换对象; c+ x0 `6 \1 |9 _6 t. l
3 U3 z( O* {0 B) B" N+ C5 d
Swap(a, a[j]);* y8 A" L- g4 q! _" s+ Y
+ t7 E/ I0 {# }$ Y$ V5 O1 N8 t+ Y; a, Q$ h
}  |8 w+ m# e1 R+ {; s

, F5 P- B$ M( Jif (j - l + 1 == k) return pivot;% \( g5 ~, y& p1 ]- ~8 F, @

$ l& a6 d1 O6 x0 v7 O// 设置p i v o t" Q9 X) t, Q9 ]4 B( l! a. B

1 z2 M+ W7 t. M# M' j6 ta[l] = a[j];2 ~' r4 |3 D/ A$ I3 x/ S3 C1 [
6 b1 w) Q2 e7 ]2 e
a[j] = pivot;
2 l! Y: U& f) p0 L5 M6 n$ K
6 w9 l% V& `. C! O// 对一个段进行递归调用
' g$ g; b, X! l/ V5 r0 Y0 p# }) Q/ e' w( O
if (j - l + 1 &lt; k)
& M2 z7 o  x3 }$ j$ f0 U6 h% A: ~! _
  @8 m+ R9 ~$ n5 K) T& F+ Rreturn select(a, j+1, r, k-j+l-1);1 X/ _1 F3 T$ O) `
7 ?: |% t9 |4 g2 C; W: U
else return select(a, l, j-1, k);
+ x# x: T) G2 i4 C* R, i% e" j, Y
* r. U3 R! X0 c- k0 @}. r2 z$ v- ^) u  F+ h
0 T! x. N( L# F3 B8 d
程序1 4 - 7在最坏情况下的复杂性是( n2 ),此时left 总是为空,而且第k个元素总是位于r i g h t.) j3 h; n, S- l$ T
5 g# [! z9 `5 K6 |
如果假定n 是2的幂,则可以取消公式(2 - 1 0)中的向下取整操作符。通过使用迭代方法,可以得到t (n) = (n)。若仔细地选择支点元素,则最坏情况下的时间开销也可以变成(n)。一种选择支点元素的方法是使用“中间的中间( m e d i a n - o f - m e d i a n)”规则,该规则首先将数组a中的n 个元素分成n/r 组,r 为某一整常数,除了最后一组外,每组都有r 个元素。然后通过在每组中对r 个元素进行排序来寻找每组中位于中间位置的元素。最后根据所得到的n/r 个中间元素,递归使用选择算法,求得所需要的支点元素。1 j+ F& M, m, x" G, A3 ]
6 x# s5 L/ `8 g, ^6 T3 P
例2-6 [中间的中间] 考察如下情形:r=5, n=27, 并且a= [ 2,6,8,1,4,1 0,2 0,6,2 2,11,9,8,4,3,7,8,1 6,11,1 0,8,2,1 4,1 5,1,1 2,5,4 ]。这2 7个元素可以被分为6组[ 2 , 6 , 8 , 1 , 4 ],[ 1 0 , 2 0 , 6 , 2 2 , 11 ],[ 9 , 8 , 4 , 3 , 7 ],[ 8 , 1 6 , 11 , 1 0 , 8 ],[ 2 , 1 4 , 1 5 , 1 , 1 2 ]和[ 5 , 4 ],每组的中间元素分别为4 , 11 , 7 , 1 0 , 1 2和4。[ 4 , 11 , 7 , 1 0 , 1 2 , 4 ]的中间元素为7。这个中间元素7被取为支点元素。由此可以得到l e ft= [ 2 , 6 , 1 , 4 , 6 , 4 , 3 , 2 , 1 , 5 , 4 ],m i d d l e= [ 7 ] ,r i g h t= [ 8 , 1 0 , 2 0 , 2 2 , 11 , 9 , 8 , 8 , 1 6 , 11 , 1 0 , 8 , 1 4 , 1 5 , 1 2 ]。& ?0 t" n  w3 ]& Q) r$ R# Z( m+ X3 g
! o+ i6 e3 G9 _! y
如果要寻找第k个元素且k&lt; 1 2,则仅仅需要在l e f t中寻找;如果k= 1 2,则要找的元素就是支点元素;如果k&gt; 1 2,则需要检查r i g h t中的1 5个元素。在最后一种情况下,需在r i g h t中寻找第(k- 1 2 )个元素。/ z; `% k1 t# m* j
# R9 H, o0 F) ]/ g4 ?/ q9 ~
定理2-2 当按“中间的中间”规则选取支点元素时,以下结论为真:$ ^: `5 }5 |  t# r  F
* K" m4 D: q$ c  ~2 I
1) 若r=9, 那么当n≥9 0时,有m a x { |l e f e|, |r i g h t| }≤7n / 8。9 {5 w& E) C2 o  u* \; V/ H, q
% N! @! y: p: U" x# ~' `; Y
2) 若r= 5,且a 中所有元素都不同,那么当n≥2 4时,有max{| left |, | right | }≤3n/ 4。
+ i1 v0 W" o3 R- ?
' |( B9 F6 f8 }. l6 X/ \证明这个定理的证明留作练习2 3。
: ~6 ~; g# p, j5 I6 I; m. n
/ V( ~. l3 a+ \6 {根据定理2 - 2和程序1 4 - 7可知,如果采用“中间的中间”规则并取r= 9,则用于寻找第k个元素的时间t (n)可按如下递归公式来计算:7 I" G$ r, n0 b* p
7 ?, k7 B0 W' s' z
在上述递归公式中,假设当n<9 0时使用复杂性为nl o gn的求解算法,当n≥9 0时,采用“中间的中间”规则进行分而治之求解。利用归纳法可以证明,当n≥1时有t (n)≤7 2cn (练习2 4 )。3 W* u) m5 S/ c: Y0 o* P
- W5 ~/ h. p# q% n' n$ t4 S5 W
当元素互不相同时,可以使用r= 5来得到线性时间性能。</P>




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5