- 在线时间
- 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时被返回。/ N. j# f0 M6 S' k
; w; Q( L O# S' V" T3 Y1 M! v2 ^
选择问题的一个应用就是寻找中值元素,此时k = [n / 2 ]。中值是一个很有用的统计量,例如中间工资,中间年龄,中间重量。其他k值也是有用的。例如,通过寻找第n / 4 , n / 2和3 n / 4这三个元素,可将人口划分为4份。
$ R# `4 m. ^ u$ {3 {0 r& p
/ W& H: y# {! p. Y2 ~选择问题可在O ( n l o g n )时间内解决,方法是首先对这n个元素进行排序(如使用堆排序式或归并排序),然后取出a [ k - 1 ]中的元素。若使用快速排序(如图1 4 - 11所示),可以获得更好的平均性能,尽管该算法有一个比较差的渐近复杂性O( n2 )。
% b! ], R$ Y) p& C3 D
5 B( X9 M* @! Q) c' \) m可以通过修写程序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)。
) z. N7 i# Y& n$ ?
2 E% G) S1 H7 d! O( `程序14-7 寻找第k 个元素4 Q& i% ^( g' w3 i* C' \) f6 W9 o
# O2 R6 r7 l% n, k0 ]& \8 \' a
template<CLASS T>2 Q" f5 K( } t t2 [
! M# Z! g' K! W/ a+ L# rT Select(T a[], int n, int k)1 h5 t) o+ V" [& i9 n1 @
' ~0 F6 ?; y( k8 H2 e& H1 B; y0 Y
{// 返回a [ 0 : n - 1 ]中第k小的元素4 l: `% a( h) Z
5 F3 Y# c" c' @" X1 z
// 假定a[n] 是一个伪最大元素
$ g! _7 ^/ |6 W g2 f6 L8 o5 B+ s( I T" n( ^) I
if (k < 1 || k > n) throw OutOfBounds();
6 d$ s2 }$ {* y- f5 i5 J# o
# Q$ }5 A1 y) d' f0 @return select(a, 0, n-1, k);
' S& s1 F1 Y9 c; E& `" D% H6 ]
, K6 c1 |9 R- `7 K' x4 f}
1 h% r' I; B. B# f( J- y5 `+ T- H9 R$ m* Y: M
template<CLASS T>- k" y7 Z$ M. \. K
% z: L# e6 [9 T! P; C
T select(T a[], int l, int r, int k)
: m* P, g# g4 x/ i. ]2 O6 i) w/ m0 ~$ _: _) c2 @
{// 在a [ l : r ]中选择第k小的元素2 G$ n4 G7 m3 x" r+ R
, @( I; d+ Y: k1 ~
if (l >= r) return a[l];
) g9 v' [( B" V' T5 v% u2 Y6 c! x4 ]6 K0 k$ D4 C
int i = l, // 从左至右的游标, v$ s; M5 F' m: l
+ K z& {+ e9 y$ ^$ N2 _& ej = r + 1; // 从右到左的游标% o$ P0 ^' R7 |. F1 v
2 p8 {2 E, w; F* ?" I* ~ C7 B; C2 mT pivot = a[l];
% X8 u2 R4 {- u: g; o' W' V2 T- f/ G- g% Q. }( s0 p" A
// 把左侧>= pivot的元素与右侧<= pivot 的元素进行交换$ q1 X9 f; j* B( m, x6 l4 v# y0 F
6 `" L8 s: j5 R. D% u
while (true) {# r s* v9 o" R4 N- k% h8 F
9 E- n2 [/ ?, I4 s7 g6 Wdo {// 在左侧寻找>= pivot 的元素( F! U; W! G' A' \- O4 Q
8 q' V T# W8 ~1 wi = i + 1;) e2 j( z2 }- s2 J
, T1 P( P# q/ k9 ~% v} while (a < pivot);
Z- D/ p0 g, O1 d, H5 Z9 ^9 W0 T
/ q5 c& H& w# h' [8 vdo {// 在右侧寻找<= pivot 的元素$ A, y6 V/ w/ L( q# W7 j5 O# [
9 U2 K! |+ [& ~: Oj = j - 1;) p( h8 t7 @0 u' i, T
/ D6 {6 R9 i! p0 r1 _
} while (a[j] > pivot);8 W/ n- d& r7 P* ~9 U0 M# I- n
}5 t. e) D- z$ h7 I7 U N
if (i >= j) break; // 未发现交换对象- z+ q# Y. m& @& j/ @; }2 I
3 e4 c% V' n% HSwap(a, a[j]);$ N x( ]" u1 J" \) ~
* ^! q* O& E" `) }3 H
}6 X+ X1 U2 `. {9 y6 t1 C
* s' y% A2 c s; k0 S, g
if (j - l + 1 == k) return pivot;/ Y( A" F3 f! y# E/ h q4 H! c
) _( g7 @ i- |
// 设置p i v o t! @8 @8 B) R) d; k H: Y$ D- R
: [: `) |. y( I$ K
a[l] = a[j];" X& `5 H, j% h; S1 ~) i7 N
8 z3 r' {$ `& z% ]a[j] = pivot;7 Z$ Y& D* ]& M& W; U0 p) m! S
3 ?/ x/ h5 r9 X! a// 对一个段进行递归调用& ^+ k) d; t! Q. Z
2 S3 Y) @2 w; L( }+ U' Rif (j - l + 1 < k)
1 P, n( y6 ^: k7 H I4 X) F( s2 h, k
return select(a, j+1, r, k-j+l-1);
7 m2 s$ X3 x( ^# M
( f8 [+ O- J. h2 h! pelse return select(a, l, j-1, k);5 u. b7 Y! P% n" Z$ `1 F
7 N3 ?& Z0 O9 Z# q}
7 N( l4 u9 b( {5 _' h5 k q% n4 w6 i7 _6 K& l* V$ [' L- p
程序1 4 - 7在最坏情况下的复杂性是( n2 ),此时left 总是为空,而且第k个元素总是位于r i g h t.& i# A! V7 l9 _) Z
9 N0 u2 l. h) ?; f如果假定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 个中间元素,递归使用选择算法,求得所需要的支点元素。- n3 q2 m* g% C% N
- h1 d# r0 J3 |% W7 a; B
例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 ]。" N' ^6 M! q1 s4 q$ D# R- f
. h e& U) d3 u t6 | P
如果要寻找第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 )个元素。
7 d! P4 y/ J% K d% _
( u7 S3 n7 M0 S- J( b( j7 q5 e定理2-2 当按“中间的中间”规则选取支点元素时,以下结论为真:# z) H" `0 E# ^* e2 K
- n! b) o7 d" M: s1) 若r=9, 那么当n≥9 0时,有m a x { |l e f e|, |r i g h t| }≤7n / 8。9 T' L1 o# o* S: `2 o8 W7 ?
/ s5 |3 I0 I. y, g& `% B) |2) 若r= 5,且a 中所有元素都不同,那么当n≥2 4时,有max{| left |, | right | }≤3n/ 4。8 |- o) m& R7 M1 J
9 E0 V5 t: {, H' T5 N4 {
证明这个定理的证明留作练习2 3。3 e8 P: H$ t9 N" y1 o0 w4 w
2 ~8 V3 N# x4 ^, t( d( {6 x5 C
根据定理2 - 2和程序1 4 - 7可知,如果采用“中间的中间”规则并取r= 9,则用于寻找第k个元素的时间t (n)可按如下递归公式来计算:
' T' t% Q: e- L7 D; X8 h$ w
$ S# e! o, B i. O+ f+ a* L$ U% B在上述递归公式中,假设当n<9 0时使用复杂性为nl o gn的求解算法,当n≥9 0时,采用“中间的中间”规则进行分而治之求解。利用归纳法可以证明,当n≥1时有t (n)≤7 2cn (练习2 4 )。
5 ]. @8 I$ D: A l7 s' w. r; X9 Y/ U( f9 v/ S. K
当元素互不相同时,可以使用r= 5来得到线性时间性能。</P> |
zan
|