- 在线时间
- 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时被返回。! D% K% L) r# F7 o6 W! w( ?
+ h& b5 T3 \. T3 }
选择问题的一个应用就是寻找中值元素,此时k = [n / 2 ]。中值是一个很有用的统计量,例如中间工资,中间年龄,中间重量。其他k值也是有用的。例如,通过寻找第n / 4 , n / 2和3 n / 4这三个元素,可将人口划分为4份。
: ^& I# p3 R ]2 [* E2 F* K# b3 L2 |* W' g0 P. A: H- }
选择问题可在O ( n l o g n )时间内解决,方法是首先对这n个元素进行排序(如使用堆排序式或归并排序),然后取出a [ k - 1 ]中的元素。若使用快速排序(如图1 4 - 11所示),可以获得更好的平均性能,尽管该算法有一个比较差的渐近复杂性O( n2 )。6 L I% @2 j; |) t) y
8 e2 C/ }' i& n# e/ k- L: n
可以通过修写程序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)。+ N+ k1 H, [: S" ]* ?% z
( H. V1 W0 _$ E) ~
程序14-7 寻找第k 个元素& |% u! s' G1 H$ X+ q
4 ?6 d- I8 }8 E! @9 A4 gtemplate<CLASS T>
: z r& B# b& \$ L" j+ M3 `5 c- y
T Select(T a[], int n, int k)6 t4 w: a9 d2 D
' ]4 |$ H/ h0 p0 _{// 返回a [ 0 : n - 1 ]中第k小的元素
9 v9 d" _5 B; o) q3 K$ D' x$ S. O2 m1 G0 N" p
// 假定a[n] 是一个伪最大元素
4 ^4 z; i8 _/ y' f
! g0 L8 t& |1 h: A7 q* b! U1 d; Lif (k < 1 || k > n) throw OutOfBounds();
2 e6 j" Y* N" N1 { E; g
" l5 I; {! w- R5 ureturn select(a, 0, n-1, k);
7 }( B6 i$ D- ~7 K
$ c; f: c1 Y6 E, ^- F}
: N+ @! P: T& B2 J) b- @2 w
7 i& a7 B6 G5 U* F& l- u2 Itemplate<CLASS T># W0 l4 J$ h3 `! J
6 m9 k Q) r. _7 X$ M4 y7 d
T select(T a[], int l, int r, int k), u" D0 ?& j0 b# D3 B! r
8 @. ^% s7 q" d5 ]. x; U6 j{// 在a [ l : r ]中选择第k小的元素
; x8 w* j0 `$ y0 q% h9 B1 ]7 E! P/ z/ [# K* w; ?% R3 k
if (l >= r) return a[l];, F5 F7 V2 u" x! v) X
$ V5 Y' ^' W/ k2 w
int i = l, // 从左至右的游标9 }- y! P0 q' _6 V5 i
$ V! O* X4 h" K* J0 E$ \j = r + 1; // 从右到左的游标5 J- X2 I, b/ K8 }' q5 d+ V$ A+ r
+ M, }! [& e2 d3 H5 j" E4 G5 K5 a. C
T pivot = a[l];
Y. L. w5 M* a
7 r! J4 L7 q4 }* s; K4 G// 把左侧>= pivot的元素与右侧<= pivot 的元素进行交换( K9 O; p2 Z7 I: a! k7 k- d4 [
8 ^# C9 U, `7 Y/ H0 |+ q0 [
while (true) {
8 U$ P* j( J7 O3 z
; }7 U# U$ {& G, F9 W$ ]% j% V8 odo {// 在左侧寻找>= pivot 的元素
2 n; m9 v6 A) `8 P
* M# x4 ?- Z3 j' Z3 W! gi = i + 1;
% M! `9 G" j! H$ [' ?" c0 Z w9 d& x. w' p5 `
} while (a < pivot);7 e! v% @) N( ^$ t7 f+ [, n* ?
3 T5 X; H8 ^/ O% {% u# O
do {// 在右侧寻找<= pivot 的元素" R% V$ O* F% [0 ^' F( K" e$ M( E* e
2 c% ^- P+ y0 |, [# E7 Q
j = j - 1;1 d$ N, r$ E5 m. y& y4 _9 j
+ J# J# H" E# z; @6 [} while (a[j] > pivot);/ q* t) j1 m7 g3 x+ Z
/ I8 N0 C5 v+ Z" ^! ` i; ~" p2 |if (i >= j) break; // 未发现交换对象
2 A, }2 e# b! e1 s$ m5 `* G$ v1 A4 p: {: \0 F, Z
Swap(a, a[j]);
2 L" w8 P6 x/ I) H9 M
7 ]" H+ X# C: W( u+ p}6 o& ?9 G0 Q) H" j# _5 A
) _. @6 T6 [; ^* |
if (j - l + 1 == k) return pivot;+ U, D3 Z/ A. T
3 {3 J% T8 j: }0 i# v) c% h
// 设置p i v o t
7 v; A( k2 {% E9 ~1 q+ a
& o( c" z5 V+ x/ {0 p2 [2 ?8 la[l] = a[j]; C) N4 O Y# L v: k0 _
5 K5 @5 I' z/ ]9 y7 }* R0 H
a[j] = pivot;
9 [" S {5 M; V& E: p$ |8 E( S7 G( F E* y) n- v
// 对一个段进行递归调用1 J2 p+ [/ K [2 K( B; Z3 \
2 D# B H" B! Q# g% Rif (j - l + 1 < k)7 [ Q+ a/ j$ n- |
. x8 g: ~1 K% H$ \! B
return select(a, j+1, r, k-j+l-1);
$ w8 O; K: S+ i& N* p
6 c+ {4 G$ E# M7 D+ Kelse return select(a, l, j-1, k);
! O$ l8 @ c! Q# A% t$ I4 y+ }3 [) o
4 a5 h% M' @! C1 v9 n}' k# k7 t8 B: f1 ?, t& C
\5 @1 A3 r! W1 o% e- t程序1 4 - 7在最坏情况下的复杂性是( n2 ),此时left 总是为空,而且第k个元素总是位于r i g h t.+ p$ ~) ^3 S# ^7 v' v
6 ~& ^$ L* q1 A+ ^/ O4 l& z9 D
如果假定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 个中间元素,递归使用选择算法,求得所需要的支点元素。) c# u; D) t0 E; h
6 ~8 i' v+ h; ?' @
例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 ]。1 A- k/ V9 N/ i( v( q9 }
7 B" f1 w# d) P# l$ A
如果要寻找第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 )个元素。 V2 _% ~3 I) N1 k. a, J
5 L8 m9 z3 l* ~' ]定理2-2 当按“中间的中间”规则选取支点元素时,以下结论为真:! s4 ~0 q6 q* j/ V( w7 `. T
, p: s0 l/ K- N: A* J9 ~2 m1) 若r=9, 那么当n≥9 0时,有m a x { |l e f e|, |r i g h t| }≤7n / 8。
_) X4 b5 Z6 ^
1 c8 Q/ O. Q( Y W7 i0 d, w2) 若r= 5,且a 中所有元素都不同,那么当n≥2 4时,有max{| left |, | right | }≤3n/ 4。
1 i4 W1 U) }$ l, m0 J5 E
) e" l& Y- y" O# v' b1 U: _证明这个定理的证明留作练习2 3。/ ?! g$ w \+ p, c, c' G' V5 Q
' e( s# f/ s: g5 f$ f根据定理2 - 2和程序1 4 - 7可知,如果采用“中间的中间”规则并取r= 9,则用于寻找第k个元素的时间t (n)可按如下递归公式来计算:
' N6 m# ~, b& I7 m/ d$ e8 y7 p4 P3 V5 a# Q8 a) R: \
在上述递归公式中,假设当n<9 0时使用复杂性为nl o gn的求解算法,当n≥9 0时,采用“中间的中间”规则进行分而治之求解。利用归纳法可以证明,当n≥1时有t (n)≤7 2cn (练习2 4 )。7 e& |% h" q- \2 \. y
5 \+ e! t3 z8 ^9 b* F$ ]当元素互不相同时,可以使用r= 5来得到线性时间性能。</P> |
zan
|