数学建模社区-数学中国

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

作者: 韩冰    时间: 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时被返回。6 _8 y* R) x" v2 X

' J7 x% ^3 C; K* ~7 R9 u7 `选择问题的一个应用就是寻找中值元素,此时k = [n / 2 ]。中值是一个很有用的统计量,例如中间工资,中间年龄,中间重量。其他k值也是有用的。例如,通过寻找第n / 4 , n / 2和3 n / 4这三个元素,可将人口划分为4份。9 K0 \. W4 V  m% f5 o& n

; ]" g/ ?$ ~* D选择问题可在O ( n l o g n )时间内解决,方法是首先对这n个元素进行排序(如使用堆排序式或归并排序),然后取出a [ k - 1 ]中的元素。若使用快速排序(如图1 4 - 11所示),可以获得更好的平均性能,尽管该算法有一个比较差的渐近复杂性O( n2 )。
4 z. X! e" s% K4 a  R8 }$ H" D4 i6 D- k. f' d) P
可以通过修写程序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)。
: t( `# ]& f: {4 z2 m2 ~
, b2 h  I) r. S% t8 w程序14-7 寻找第k 个元素
# k- r  u$ y" Q' t/ ?/ i( V; {& @
/ H  C* ?$ ]0 H) ]7 B' K0 ?  o* Utemplate<CLASS T>8 J, d5 q1 l6 p- i: D0 M1 B

: w; n, G  O' U. y, b. q" tT Select(T a[], int n, int k)
* C5 d' E( t$ \2 N$ ^3 G9 a# f) h0 {& L5 C
{// 返回a [ 0 : n - 1 ]中第k小的元素5 P0 t' d/ A. q9 K
! G' \& F7 p. Q9 ~4 W0 O
// 假定a[n] 是一个伪最大元素9 L: ~: M! h+ m9 [

$ z* e3 ~- i& ^5 |0 }if (k &lt; 1 || k &gt; n) throw OutOfBounds();
: p: g! @5 _$ o. B! J- X! @% }5 e; Y7 \3 E
return select(a, 0, n-1, k);
# s- ^5 ^+ }* X5 H2 L; T: i
! i  S9 G, q7 W0 M* w5 q" F- Q}, ~; q0 C/ z, _; ~) t' Z- D
, U7 d$ M/ s7 n  ?$ R  Q4 \9 C# M
template<CLASS T>
& b. B/ J5 j) \5 K/ l' C( I/ q, \1 ]* c2 X: ^
T select(T a[], int l, int r, int k)% q0 v9 z" r& s9 G

* m. e5 g! `; c5 Z. ?{// 在a [ l : r ]中选择第k小的元素
# j- k" d& `' j9 a, w6 y
: K7 o  H( T9 g7 y/ c5 p2 Qif (l &gt;= r) return a[l];$ j! v& P# ^3 W9 e3 S; p" W& s7 C
6 }6 N0 T, w5 ]7 u0 O& D" }
int i = l, // 从左至右的游标+ E3 P2 R  i0 m" G; F( `) d, f

! z: f2 }. B; n5 ?) {j = r + 1; // 从右到左的游标
( r1 r$ I0 v+ y' ]. h% ]: x" h' o; V) c; Z/ w3 a
T pivot = a[l];
) j) N" X# F1 Y9 F1 X: g. t
+ A, f6 i+ |. p// 把左侧&gt;= pivot的元素与右侧&lt;= pivot 的元素进行交换- H- J1 J  |, ?  ~! o5 H0 h3 n0 T
! e( p; ?/ Z1 s. F8 Z, ~6 i
while (true) {" `' _; w5 V" {( m" F( z5 Q

+ U5 h0 Z3 L! I' D& o4 E' R, Wdo {// 在左侧寻找&gt;= pivot 的元素. W% s" ~, O: c! G, J& Y9 s
; @; L, t  o* h5 g  d/ i4 I% L
i = i + 1;; \& l2 e! P& L8 p2 b, Q; U

" b2 }2 x1 c" Y. o: v$ L} while (a &lt; pivot);' E2 q8 d$ n, K3 _2 o

& ?; y, U$ ?+ Wdo {// 在右侧寻找&lt;= pivot 的元素
- _' _: M  S* o8 G5 X* `5 `) a! t5 N! B* M
j = j - 1;- M" U3 o: e; |0 P+ T5 l
! J3 W8 w6 o. U5 y0 `
} while (a[j] &gt; pivot);; o; Z' g' V$ v" s
3 @" y2 X$ j4 ~5 _
if (i &gt;= j) break; // 未发现交换对象. C; S% B& m; p4 w0 T: A
% G3 S5 W3 K, D3 p4 O6 T" v
Swap(a, a[j]);
+ j0 U( Z% e6 Z! ?: @( l/ Q  K5 L* I% \7 A- j2 t
}# p" f; t. V( S2 Q9 C
$ @  _4 r6 }5 |
if (j - l + 1 == k) return pivot;
6 k& p- x9 F& F: `- z& @; k# k# [* y1 d; v# X  [: K7 Q& j& f# Z5 v
// 设置p i v o t8 c3 V8 o5 M. u; K3 L. b! s" ^
7 v! a7 ]8 L6 @) ~
a[l] = a[j];
/ g# s! b- a; U. K5 w6 W3 a$ D  X) Z
a[j] = pivot;& D' q5 N! R( o; ?3 n
9 R1 g, M4 S) Q9 h
// 对一个段进行递归调用
4 L8 w  v1 s6 ]0 r7 u8 i& W7 n; z1 M. n0 [5 N, C+ k8 u
if (j - l + 1 &lt; k). Z+ {( l: Q1 t$ ^

; j# o" i3 g* r% b2 nreturn select(a, j+1, r, k-j+l-1);6 O% y9 @. I+ T2 R. A
  |: D$ i; x: C; u; x; N- e1 O
else return select(a, l, j-1, k);
$ h/ h/ N' I- [3 P! m& T/ l* z- Y" o& g" ?6 @2 ?" \# s2 K; ~4 O
}, |0 s, A7 _, R) ^( U% H
: H& `& r  Z! _# @$ U. Z
程序1 4 - 7在最坏情况下的复杂性是( n2 ),此时left 总是为空,而且第k个元素总是位于r i g h t.
0 H' W% a$ }& ]- E$ F4 Z- s3 e/ C5 }  H( q  B5 _  c! D- x
如果假定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 个中间元素,递归使用选择算法,求得所需要的支点元素。
# o7 b1 D. p0 Y  w! \7 m# l9 W( r2 h3 {3 q( r
例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 ]。" z4 p7 R5 _0 Q, z6 }: K3 ~

1 T1 z( [$ T, V9 q: R+ ?2 i' q如果要寻找第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 )个元素。7 s3 F/ o( @+ ]" K/ b

8 \. ^( a; o4 V  S) r" n& c# z定理2-2 当按“中间的中间”规则选取支点元素时,以下结论为真:
) ?3 C, w9 P# i+ _0 X9 u5 f5 E( ^% i0 `6 W  B0 n' m, `
1) 若r=9, 那么当n≥9 0时,有m a x { |l e f e|, |r i g h t| }≤7n / 8。
. H! S% {( A) {$ \+ c4 w
& \/ o4 d" H3 ^' o/ L2) 若r= 5,且a 中所有元素都不同,那么当n≥2 4时,有max{| left |, | right | }≤3n/ 4。
& E* q( p- E7 w/ `# S
! ?2 g' R. t9 I1 C: e6 h证明这个定理的证明留作练习2 3。' {1 ^4 ~7 U2 D* D% A% @  W
, a% L3 o  [9 A$ r8 k$ e8 W2 f4 H
根据定理2 - 2和程序1 4 - 7可知,如果采用“中间的中间”规则并取r= 9,则用于寻找第k个元素的时间t (n)可按如下递归公式来计算:
0 Z+ ~8 l& C' W4 f1 p' b. O
! T% E0 X) P4 ~0 {4 d在上述递归公式中,假设当n<9 0时使用复杂性为nl o gn的求解算法,当n≥9 0时,采用“中间的中间”规则进行分而治之求解。利用归纳法可以证明,当n≥1时有t (n)≤7 2cn (练习2 4 )。
7 a) v# j" G3 A6 N+ r- @" L7 x; _1 n' n
当元素互不相同时,可以使用r= 5来得到线性时间性能。</P>




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