- 在线时间
- 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时被返回。
' P( _& ~. a8 a4 I. R
+ ^( I2 C7 J* W: J8 H% [选择问题的一个应用就是寻找中值元素,此时k = [n / 2 ]。中值是一个很有用的统计量,例如中间工资,中间年龄,中间重量。其他k值也是有用的。例如,通过寻找第n / 4 , n / 2和3 n / 4这三个元素,可将人口划分为4份。
, T& F2 F- \/ \8 [$ W: J3 `
. ^3 Y# [' W. G: L/ i5 [选择问题可在O ( n l o g n )时间内解决,方法是首先对这n个元素进行排序(如使用堆排序式或归并排序),然后取出a [ k - 1 ]中的元素。若使用快速排序(如图1 4 - 11所示),可以获得更好的平均性能,尽管该算法有一个比较差的渐近复杂性O( n2 )。' q k5 I! \. x) K
$ ` [7 e7 ?) [
可以通过修写程序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)。
# k; Y) c% E: u( n' y" f4 M1 G1 Q r% q# c7 T0 C
程序14-7 寻找第k 个元素2 |* v& r& x; e" i% P) \. `; z
9 G, `; ?0 R7 O8 C8 h2 S# a
template<CLASS T> L( Z7 W5 b' }/ ]) p' J' {
7 P' _# x. i3 ?3 o9 A U
T Select(T a[], int n, int k)
* Y1 j4 m% L* H; |# m
' r% p$ }% ?6 J6 m{// 返回a [ 0 : n - 1 ]中第k小的元素
+ Z4 S: V+ P0 n
" O( o& m/ g5 \1 A// 假定a[n] 是一个伪最大元素
3 J5 \ {0 F! O7 |' K2 G
* b9 @6 A5 L/ m9 `& Qif (k < 1 || k > n) throw OutOfBounds();
# l% Y( u2 {: B; E' p7 J( y9 v
% C$ y- @. o7 _$ j& C0 S. L9 z8 Preturn select(a, 0, n-1, k);
- k' k- s" S; E) C! b0 B; Q
2 h) X+ l3 d" p% A: i}" T. ~# X6 R- C! s# Q3 Q5 o2 R" B
+ G8 n0 m4 f4 [( f$ z: j1 mtemplate<CLASS T>
0 `4 Z4 ^; w) ~' T0 U' {0 P9 {, B& d5 Y; k
T select(T a[], int l, int r, int k)
0 E8 {" I) R' r# ?3 a( j. b
" V4 K, ?" \# t2 I# `1 r5 v{// 在a [ l : r ]中选择第k小的元素
8 ~4 b4 }# s* P) v, L0 ]+ y; o. k/ r% @
if (l >= r) return a[l];) x, X4 {" c0 Q$ v7 T* p
( _" k" H5 t7 z, _8 S! B/ |; y* Yint i = l, // 从左至右的游标4 ^! N: [7 |) S2 q9 _
+ X- y' T+ r" Q, E1 |7 W+ b M9 [j = r + 1; // 从右到左的游标
: A/ z$ `' `4 ?3 Z0 w: K: h$ X" d8 E+ s! ~
T pivot = a[l];+ u# f& Y2 v+ E( X
/ r# e) Y8 u, f$ u
// 把左侧>= pivot的元素与右侧<= pivot 的元素进行交换5 }2 D9 B/ s8 |0 F7 d' F' k8 {
1 O3 i$ T" s; Q ^$ g6 N0 D
while (true) {
B& @' T7 a, ^# }" h' @
9 u8 N6 S2 f4 W/ h" tdo {// 在左侧寻找>= pivot 的元素
H; i7 D% `4 o/ I
. Q$ Q( S- g, i, L1 \! [, ki = i + 1;+ Q. H$ B! \* S) A6 ]+ t
$ G* \( \) X! t7 C} while (a < pivot);
. Y& h: F3 \" m U; T
2 o8 M1 X& L# v6 A, Qdo {// 在右侧寻找<= pivot 的元素
% ^ i- ?5 H- g; S7 C# R7 p/ `
4 u! ?; L+ G! O5 X/ R+ M' ?j = j - 1;
9 k( J4 N% g# G( b0 { O0 m. F, {3 x3 U/ |. z. g. h9 }
} while (a[j] > pivot);; D% {- u7 }. N7 o
: y0 j4 P5 X9 \1 G& u$ V8 x
if (i >= j) break; // 未发现交换对象
( n$ d. l7 J! T' n5 o4 X1 [% i% m+ e T# D8 f- y; M
Swap(a, a[j]);4 l! K5 @% p( [6 w7 ^
$ f3 r% L2 z+ B, q5 @+ _
}8 s; D$ ~- k$ R+ V
$ o- u0 [3 B/ M) L
if (j - l + 1 == k) return pivot;9 R2 z# d2 d& Q n3 }
8 |0 F+ E# ]+ z; H
// 设置p i v o t
( e2 n5 i5 v3 V. r) Z9 P) t9 [+ O; v, V$ y j7 H+ U e R! M
a[l] = a[j];
! c7 w0 M* z' G. N0 V. N
) l0 U- w9 c' Y5 m: [a[j] = pivot;
# G0 s( j* x3 @- k% J. ^) @# \
+ ~( p0 x T# J: {/ V* g2 _2 k// 对一个段进行递归调用
/ m" K+ z9 x) T% F" f$ `1 _$ d) x) a! v, F
- l' I# D3 ]! qif (j - l + 1 < k)( T4 _ v' y' c' b' a3 K
- l! G9 f. L( E% ^return select(a, j+1, r, k-j+l-1);
! z, c. Q( z, }6 @6 |; o7 v
4 O6 e$ U9 c' h- E: _3 A Yelse return select(a, l, j-1, k);8 ~2 @9 R0 n% A2 Q
t O: Z0 [0 N4 ?1 ]}+ c3 p H4 R; X# |1 t2 T* @
' V; f. c8 U2 e0 |* n+ B2 W4 z
程序1 4 - 7在最坏情况下的复杂性是( n2 ),此时left 总是为空,而且第k个元素总是位于r i g h t.& B$ w6 @; N A2 z# p7 @) b
, t; ?: n5 x# Z5 Y" M* A' r
如果假定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 个中间元素,递归使用选择算法,求得所需要的支点元素。
0 o5 R# d+ [0 v' B; f" V) e$ m3 G$ N8 y1 d% s% h8 X
例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 ]。& B9 K$ M! v! S" S1 ]
- g8 ~6 P, u& P) B; X2 ^: E" E
如果要寻找第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 )个元素。
! h) k7 b# N" ?5 }- }: z+ ~! I+ L1 E1 h) h0 T: f; K
定理2-2 当按“中间的中间”规则选取支点元素时,以下结论为真:7 u7 v8 \, R' I/ f5 l1 o& s$ f- O% @
+ s9 ~( h8 B) z @1) 若r=9, 那么当n≥9 0时,有m a x { |l e f e|, |r i g h t| }≤7n / 8。8 H+ |- q4 H9 i* ^1 i% ~
6 l( Z: E# x. o! U4 f( q; L
2) 若r= 5,且a 中所有元素都不同,那么当n≥2 4时,有max{| left |, | right | }≤3n/ 4。
' s5 `, ~; N( k: o9 b" k- l' T! t% s7 a; m0 T, z( e
证明这个定理的证明留作练习2 3。
2 }; _1 x& W, X0 y0 g. j: H
8 d1 U# w$ J5 D3 I7 ^. p根据定理2 - 2和程序1 4 - 7可知,如果采用“中间的中间”规则并取r= 9,则用于寻找第k个元素的时间t (n)可按如下递归公式来计算:) }2 g& B* ?7 T1 q4 A9 C
3 ]0 w5 j) h, R1 y3 e在上述递归公式中,假设当n<9 0时使用复杂性为nl o gn的求解算法,当n≥9 0时,采用“中间的中间”规则进行分而治之求解。利用归纳法可以证明,当n≥1时有t (n)≤7 2cn (练习2 4 )。+ ]" _& e% i% H1 W9 c
r8 J* N* E; D( t/ J5 t# Z7 e d当元素互不相同时,可以使用r= 5来得到线性时间性能。</P> |
zan
|