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

我的地盘我做主
该用户从未签到
 |
< >对于给定的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时被返回。, h/ G6 E6 r& J6 H; D5 v
. q8 p" u2 X* h) o+ t7 c0 l4 Z
选择问题的一个应用就是寻找中值元素,此时k = [n / 2 ]。中值是一个很有用的统计量,例如中间工资,中间年龄,中间重量。其他k值也是有用的。例如,通过寻找第n / 4 , n / 2和3 n / 4这三个元素,可将人口划分为4份。
! | _8 U1 O; Z+ z( b+ R# \
% u/ s7 E. {' y8 W' M- J选择问题可在O ( n l o g n )时间内解决,方法是首先对这n个元素进行排序(如使用堆排序式或归并排序),然后取出a [ k - 1 ]中的元素。若使用快速排序(如图1 4 - 11所示),可以获得更好的平均性能,尽管该算法有一个比较差的渐近复杂性O( n2 )。
" [1 k6 ] t+ x6 P( G1 _+ E" u C/ j# Y6 p. v' O8 e
可以通过修写程序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 < 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)。% ?* E( R2 K! I7 G& u i
$ x* j$ p; X* `" H+ G+ y程序14-7 寻找第k 个元素% d1 ~" a& O% N4 L
3 [ r: r: _2 _$ i( vtemplate<CLASS T>
/ v' U1 w6 _- p& G# C9 @4 S" P0 ^ p# C+ t: H1 z
T Select(T a[], int n, int k)
0 m2 @# y% V/ v8 q' F
+ N- Q+ e% i, _{// 返回a [ 0 : n - 1 ]中第k小的元素
- G# b+ H1 v* g
- @+ O' y. }, B) Z1 O( w// 假定a[n] 是一个伪最大元素
- q; K4 F9 L6 C* _1 O9 z: E; f( b2 x2 R& j3 p0 F, o
if (k < 1 || k > n) throw OutOfBounds();- G; @! v# m' ?- ~7 J' C' G! p
4 c* C$ `' m, j8 c6 k, H# V8 P7 @; o7 Vreturn select(a, 0, n-1, k);
; w h; v3 d4 L6 R
L3 M& R5 s& `: \ J# x" h}
: V' h; w7 _- X' B) t$ v+ Q6 w4 r5 l u, i9 @, j1 L
template<CLASS T>; a/ O! a" p7 S* g
/ q |% C, B& y1 C% ?! DT select(T a[], int l, int r, int k)- I! i1 F- D& t' H
3 a8 B: D. m7 Y1 l" e8 n; I) w
{// 在a [ l : r ]中选择第k小的元素
% C9 l6 N0 i' U$ G' j3 m' x* k" Y/ o% Q3 ~
if (l >= r) return a[l];/ J% ?3 _. D- S. r8 J
5 T% @3 P$ G5 a) ?0 V! \3 [int i = l, // 从左至右的游标/ t; h. y2 I7 h4 s
; K+ K, N6 o* |% @+ z
j = r + 1; // 从右到左的游标, U5 r0 f1 F6 c! I
% @+ E `5 }# l9 |; [$ b
T pivot = a[l];
0 ~+ U9 }1 ^( a+ r r7 q( J' X& n& G
// 把左侧>= pivot的元素与右侧<= pivot 的元素进行交换6 u7 f4 C( j& W- |2 v
6 C& a5 r3 } c8 _
while (true) {
3 }$ v: L+ E9 t$ |: C: n
- }5 J$ V& n2 p4 l0 `do {// 在左侧寻找>= pivot 的元素
" w/ \$ r! i2 O
+ e, Z e( x2 F9 Yi = i + 1;: E1 W3 Q! k( X4 d: f: ~
" d( j% n* g/ _) a. f} while (a < pivot);
X* t0 l% ]- K* R+ x- ?. a6 o/ x0 s& m5 Q( F% Q d
do {// 在右侧寻找<= pivot 的元素5 N# a) J" S0 H5 ^. d6 E4 I/ T& A
4 L# L; {5 L/ E2 wj = j - 1;# }% [; [! x0 q4 c
$ I/ A$ f/ v5 i) W2 j2 q
} while (a[j] > pivot);
6 |1 l# O% }# Z% `' B2 T& v2 U- p; u
: J( T# X9 O& [' m, Y5 m% c2 yif (i >= j) break; // 未发现交换对象7 e% o2 j3 z; |3 l9 {- ?4 a
; W& x9 y u a5 k
Swap(a, a[j]);$ f5 Q- m/ A& R7 C) E6 c
" Q! A6 y4 h$ W ~}
* @! i% i( N& l3 W! E6 T% `8 n0 ^% U
if (j - l + 1 == k) return pivot;
4 K9 t3 ^6 [* t. N# ?4 ^6 r Z
; z- u( d1 g+ {0 T; |( Y V// 设置p i v o t
( g1 Q& s; B* |( n3 g( z( {) @0 B! M9 f/ J* T9 U4 s( i
a[l] = a[j]; t) c, ]* _# U+ H! o
8 H: y6 G7 ?# U) j( `( e7 f8 ka[j] = pivot;# l7 H6 N5 z5 d. k7 I
4 \0 F4 Y' `0 g+ Q4 R3 c
// 对一个段进行递归调用
' F0 u2 z; r9 h/ P% }1 }$ ]$ s) W9 D2 H
if (j - l + 1 < k)
( ?0 u& E0 ^% Y* d1 b' {# N6 s6 E% ?2 O7 x2 R
return select(a, j+1, r, k-j+l-1);; K3 u$ s9 l) m% N7 L8 d; x* e' `8 L4 D
3 T) T8 C1 T' C4 ^2 O
else return select(a, l, j-1, k);; v+ R4 _0 W# ?( ]) T1 }- x( z
0 {6 b, O+ ?: c6 g4 U9 X}( k ~2 L! |& K2 ~9 I
* u1 ^0 Z- F3 \; d& y2 m K2 F
程序1 4 - 7在最坏情况下的复杂性是( n2 ),此时left 总是为空,而且第k个元素总是位于r i g h t.
3 k6 V7 a5 n7 i7 D2 g* I9 D& C9 b
如果假定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 个中间元素,递归使用选择算法,求得所需要的支点元素。
6 J* n0 [$ R) I3 ?3 I, W* |/ u/ Y- _
例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 ]。& \8 Z. o# F4 S' s
) M0 D7 O+ p6 d# I* F v0 t如果要寻找第k个元素且k< 1 2,则仅仅需要在l e f t中寻找;如果k= 1 2,则要找的元素就是支点元素;如果k> 1 2,则需要检查r i g h t中的1 5个元素。在最后一种情况下,需在r i g h t中寻找第(k- 1 2 )个元素。
J9 ~" o7 S+ m. G& W$ S$ y1 K$ S6 H* u; d- \
定理2-2 当按“中间的中间”规则选取支点元素时,以下结论为真:# V6 [# e2 O1 P, J2 U7 z
. Y+ Y2 x7 j7 n/ \
1) 若r=9, 那么当n≥9 0时,有m a x { |l e f e|, |r i g h t| }≤7n / 8。
6 N: c) W4 Q8 a0 r
: n% A3 H; n' O$ S! h# E. ?2) 若r= 5,且a 中所有元素都不同,那么当n≥2 4时,有max{| left |, | right | }≤3n/ 4。
& d' V$ y5 z3 w, E) j8 h
' N! x: J% O7 l; W# d# l6 l& W证明这个定理的证明留作练习2 3。
$ I/ K" v1 U7 |2 I6 X. q- o/ X) C7 e; v7 o: t
根据定理2 - 2和程序1 4 - 7可知,如果采用“中间的中间”规则并取r= 9,则用于寻找第k个元素的时间t (n)可按如下递归公式来计算:
6 [7 {7 g$ o. m$ v9 V
! ?: b2 W0 z) c6 b' G在上述递归公式中,假设当n<9 0时使用复杂性为nl o gn的求解算法,当n≥9 0时,采用“中间的中间”规则进行分而治之求解。利用归纳法可以证明,当n≥1时有t (n)≤7 2cn (练习2 4 )。
' A3 ^7 J4 C8 ^; e3 T- b! ?4 o2 z4 r7 ~/ f
当元素互不相同时,可以使用r= 5来得到线性时间性能。</P> |
zan
|