o! X4 Q. S4 T为了得到更快的算法,需要简化矩阵分割和再组合这两个步骤。一种方案是使用S t r a s s e n方法得到7个小矩阵。这7个小矩阵为矩阵D, E, ., J,矩阵D到J可以通过7次矩阵乘法, 6次矩阵加法,和4次矩阵减法计算得出。前述的4个小矩阵可以由矩阵D到J通过6次矩阵加法和两次矩阵减法得出. ( o+ V# P7 Z6 K! X
9 t: a/ S: y$ ^0 @6 y% z
用上述方案来解决n= 2的矩阵乘法。将某矩阵A和B相乘得结果C,如下所示: & C4 |4 `5 N# U) u7 A ; a" R- S; L$ l6 \4 F, L因为n> 1,所以将A、B两矩阵分别划分为4个小矩阵,每个矩阵为1×1阶,仅包含一个元素。1×1阶矩阵的乘法为小问题,因此可以直接进行运算。利用计算D~J的公式,得: ) R$ l7 a# i' W# ]7 O! [* r! `. R' L% |
D= 1(6-8)=-2: o N5 W8 m- W6 O! X: {/ @
9 T- `% P* R X& oE= 4(7-5)= 8 $ S0 O/ G! r6 u9 k( [' E 3 R% e! ^% T$ ^* `F=(3 + 4)5 = 3 5/ o7 A; q9 ~* V9 e" s; M
0 Q2 _' P! T) q5 B u* \! x/ U
G=(1 + 2)8 = 2 4; C: F }8 D$ x- Y. u
) {2 L, h$ W* j" o
H=(3-1)(5 + 6)= 2 2 * d0 O0 b; |5 H: g% B8 j 8 ]$ a, ~* ]. Z8 o, cI=(2-4)(7 + 8)=-3 0# j3 j# ] g5 K( v3 _& B8 Q% I2 J# ^
8 n+ L; W m$ i2 j+ _J=(1 + 4)(5 + 8)= 6 5 ) c# u" M2 h" Y" i7 d# c7 x8 G+ L. e2 {: U6 O. r
根据以上结果可得: 2 r$ W# C4 |5 H1 \ ]- k 4 \0 b+ v. { t$ [& Z4 i! P对于上面这个2×2的例子,使用分而治之算法需要7次乘法和1 8次加/减法运算。而直接使用公式(2 - 1),则需要8次乘法和7次加/减法。要想使分而治之算法更快一些,则一次乘法所花费的时间必须比11次加/减法的时间要长。* b0 z$ x& q3 ~+ W7 m! p: j
! d0 F# s U5 j' n
假定S t r a s s e n矩阵分割方案仅用于n≥8的矩阵乘法,而对于n<8的矩阵乘法则直接利用公式(2 - 1)进行计算。则n= 8时,8×8矩阵相乘需要7次4×4矩阵乘法和1 8次4×4矩阵加/减法。每次矩阵乘法需花费6 4m+ 4 8a次操作,每次矩阵加法或减法需花费1 6a次操作。因此总的操作次数为7 ( 6 4m+ 4 8a) + 1 8 ( 1 6a) = 4 4 8m+ 6 2 4a。而使用直接计算方法,则需要5 1 2m+ 4 4 8a次操作。要使S t r a s s e n方法比直接计算方法快,至少要求5 1 2-4 4 8次乘法的开销比6 2 4-4 4 8次加/减法的开销大。或者说一次乘法的开销应该大于近似2 . 7 5次加/减法的开销。 % [6 S0 e, t4 l ) T; x6 y+ C3 |1 g8 S假定n<1 6的矩阵是一个“小”问题,S t r a s s e n的分解方案仅仅用于n≥1 6的情况,对于n<1 6的矩阵相乘,直接利用公式( 2 - 1)。则当n= 1 6时使用分而治之算法需要7 ( 5 1 2m+ 4 4 8a) +1 8 ( 6 4a) = 3 5 8 4m+ 4 2 8 8a次操作。直接计算时需要4 0 9 6m+ 3 8 4 0a次操作。若一次乘法的开销与一次加/减法的开销相同,则S t r a s s e n方法需要7 8 7 2次操作及用于问题分解的额外时间,而直接计算方法则需要7 9 3 6次操作加上程序中执行f o r循环以及其他语句所花费的时间。即使直接计算方法所需要的操作次数比St r a s s e n方法少,但由于直接计算方法需要更多的额外开销,因此它也不见得会比S t r a s s e n方法快。 - j$ ^) E* e4 Z9 g2 P1 ~* C0 k1 F; z! o! M0 A( b( l: w
n 的值越大,Strassen 方法与直接计算方法所用的操作次数的差异就越大,因此对于足够大的n,Strassen 方法将更快。设t (n) 表示使用Strassen 分而治之方法所需的时间。因为大的矩阵会被递归地分割成小矩阵直到每个矩阵的大小小于或等于k(k至少为8,也许更大,具体值由计算机的性能决定). 用迭代方法计算,可得t(n) = (nl og27 )。因为l og27 ≈2 . 8 1,所以与直接计算方法的复杂性(n3 )相比,分而治之矩阵乘法算法有较大的改进。7 |+ ?" Z# \& [3 X' c# v9 K
" H; ?/ n- W- X) L注意事项 & u, c) Q U# ]# `* [( j* \ J % K, V4 t$ s3 G5 N分而治之方法很自然地导致了递归算法的使用。在许多例子里,这些递归算法在递归程序中得到了很好的运用。实际上,在许多情况下,所有为了得到一个非递归程序的企图都会导致采用一个模拟递归栈。不过在有些情况下,不使用这样的递归栈而采用一个非递归程序来完成分而治之算法也是可能的,并且在这种方式下,程序得到结果的速度会比递归方式更快。解决金块问题的分而治之算法(例2 - 2)和归并排序方法( 2 . 3节)就可以不利用递归而通过一个非递归程序来更快地完成。+ l7 k$ b! t( I: Q' M4 G
2 U5 f1 z- V0 `. S' P3 l8 k0 v
例2-4 [金块问题] 用例2 - 2的算法寻找8个金块中最轻和最重金块的工作可以用二叉树来表示。这棵树的叶子分别表示8个金块(a, b,., h),每个阴影节点表示一个包含其子树中所有叶子的问题。因此,根节点A表示寻找8个金块中最轻、最重金块的问题,而节点B表示找出a,b,c 和d 这4个金块中最轻和最重金块的问题。算法从根节点开始。由根节点表示的8金块问题被划分成由节点B和C所表示的两个4金块问题。在B节点,4金块问题被划分成由D和E所表示的2金块问题。可通过比较金块a 和b 哪一个较重来解决D节点所表示的2金块问题。在解决了D和E所表示的问题之后,可以通过比较D和E中所找到的轻金块和重金块来解决B表示的问题。接着在F,G和C上重复这一过程,最后解决问题A。- L. O8 u7 e% E5 F8 Q3 h* n; u! g, e
8 f: f1 a, H B$ N( ~0 S8 `+ n* k可以将递归的分而治之算法划分成以下的步骤: ( C" H, `0 \/ U3 ?3 l S6 `7 ^- f$ z& P1) 从图2 - 2中的二叉树由根至叶的过程中把一个大问题划分成许多个小问题,小问题的大小为1或2。. U: o1 I7 s- m% l% a
6 u% j4 d5 b- ^5 h! e; Y2 P$ i$ j
2) 比较每个大小为2的问题中的金块,确定哪一个较重和哪一个较轻。在节点D、E、F和G上完成这种比较。大小为1的问题中只有一个金块,它既是最轻的金块也是最重的金块。 & M$ h$ d0 f& f4 C( D 4 B: u) p- s4 u3) 对较轻的金块进行比较以确定哪一个金块最轻,对较重的金块进行比较以确定哪一个金块最重。对于节点A到C执行这种比较。 : F1 d8 @5 s4 i& g 8 S. Y1 f9 a B/ z, K2 u6 z# s2 C# W根据上述步骤,可以得出程序1 4 - 1的非递归代码。该程序用于寻找到数组w [ 0 : n - 1 ]中的最小数和最大数,若n < 1,则程序返回f a l s e,否则返回t r u e。 ) b3 A# d" I# H3 [' ^* J # N- O, X& u% u+ p7 h4 D当n≥1时,程序1 4 - 1给M i n和M a x置初值以使w [ M i n ]是最小的重量,w [ M a x ]为最大的重量。 8 g/ j; w4 S7 Z, }( Q. `5 h0 k2 o x5 @% W1 O8 l$ b
首先处理n≤1的情况。若n>1且为奇数,第一个重量w [ 0 ]将成为最小值和最大值的候选值,因此将有偶数个重量值w [ 1 : n - 1 ]参与f o r循环。当n 是偶数时,首先将两个重量值放在for 循环外进行比较,较小和较大的重量值分别置为Min和Max,因此也有偶数个重量值w[2:n-1]参与for循环。 m$ @+ J! k; k) j4 G, m* |( ~
- ^' _) f) U6 t2 w; b/ Q% _
在for 循环中,外层if 通过比较确定( w [ i ] , w [ i + 1 ] )中的较大和较小者。此工作与前面提到的分而治之算法步骤中的2) 相对应,而内层的i f负责找出较小重量值和较大重量值中的最小值和' b0 M& ^/ B* d% d$ h& {9 A r- D
. E: r' o) T0 D' @
最大值,这个工作对应于3 )。for 循环将每一对重量值中较小值和较大值分别与当前的最小值w [ M i n ]和最大值w [ M a x ]进行比较,根据比较结果来修改M i n和M a x(如果必要)。# S( c D7 F" S
4 c4 s( A Z1 v: a5 H3 Q下面进行复杂性分析。注意到当n为偶数时,在for 循环外部将执行一次比较而在f o r循环内部执行3 ( n / 2 - 1 )次比较,比较的总次数为3 n / 2 - 2。当n 为奇数时,f o r循环外部没有执行比较,而内部执行了3(n-1)/2次比较。因此无论n 为奇数或偶数,当n>0时,比较的总次数为「3n/2ù-2次。 2 ^9 K4 E# \0 x/ A5 Q, b5 F / D o1 x0 v2 |6 E4 F程序14-1 找出最小值和最大值的非递归程序 2 R1 }; i' m) M" P ' p$ F9 C3 u& c: ~7 mtemplate<CLASS T>% l" r: Z2 g# R& z# ]) O
: \5 p* }* t- e/ q- r9 i# \4 i2 abool MinMax(T w[], int n, T& Min, T& Max)# w! @: f( c! c% u, C
- a& K3 n# B6 w- e7 |9 o
{// 寻找w [ 0 : n - 1 ]中的最小和最大值 - o" w) ^0 I6 F4 @0 S7 k1 k , i" k1 h D! s" i6 P4 k1 }# T// 如果少于一个元素,则返回f a l s e5 P/ h3 I0 k8 d6 k5 H
0 g- R N# G* ?: R: F% H# N// 特殊情形: n <= 1! P" X: _ Z5 E& h
L8 ?! |; |. t8 y# `) y9 b
if (n < 1) return false; / L9 ?" r) r! b( L+ y( r& _# \8 q
if (n == 1) {Min = Max = 0;" S0 W0 S) @" w" L5 s
/ Z" F- `& N4 C3 X9 w# O6 Rreturn true;}. _& f( X- Q/ N' z
3 u W, a+ z* P( d. S/ /对Min 和M a x进行初始化 & ^' D# T# b4 y8 z. Q7 g5 K + t) v2 S2 l: Yint s; // 循环起点' F/ r ?0 X7 ?0 j# s$ w
# V4 F7 V( x$ M4 N7 W) R: S
if (n % 2) {// n 为奇数& Q8 t; [ I' c: t
- c9 [0 g3 _: _+ t. Q5 C
Min = Max = 0; 9 @3 r. b' w; L5 B. S5 `) n: ^- K; l8 @ + I8 H) Y* ~6 as = 1;} ( @7 y% J5 z; V2 l; ` d* N$ J9 t, C3 Z }4 Felse {// n为偶数,比较第一对+ V' o( Q6 b j7 {1 d8 ?5 j
4 A; s E8 j" n& D) e2 K4 Vif (w[0] > w[1]) { # o( K% q6 Q% w- N; v* o+ ^3 ?0 `; u" j: E* E" [
Min = 1; 5 P# R! a8 R0 W1 f6 |" d. X& j# S2 }" o/ d8 {) x
Max = 0;} # x, h8 B# i z ]# t+ T3 v% \/ C- g F1 P4 t& T
else {Min = 0; 7 i, W% d W: g' u/ G- g* ]: C ! N1 S n0 `# [/ F6 ^. |Max = 1;}$ i9 L7 W5 w! R, G
# V6 ? M! D2 R# t4 Y, ~0 Ws = 2;}+ Q( g! X! ?% O
+ O( p; I5 ^5 e3 C
// 比较余下的数对 8 _% p0 l+ u& G ]1 y. q# w) j 7 M z/ O9 P! ?, jfor (int i = s; i < n; i += 2) {7 q7 D( Q) f8 F5 P
: V( W- B! V( c, |
// 寻找w 和w [ i + 1 ]中的较大者 7 V. {2 \ |) g4 K, r x( g# [1 v- g2 N, S1 J$ V9 M. _' m0 H
// 然后将较大者与w [ M a x ]进行比较# p% V$ Z! Y" p2 X$ \( ]
9 z2 k9 a% @* ]/ q// 将较小者与w [ M i n ]进行比较 6 U7 O% m7 a* Q: W+ z2 u ) p4 L' u+ M& A- W" B. N0 @+ sif (w > w[i+1]) { 5 D1 H1 b5 t6 L) j' t g ~+ f8 s2 s0 J, @ l
if (w > w[Max]) Max = i; ! l1 C x" R* `4 q* a* o d+ P/ k, U3 x0 f' Y
if (w[i+1] < w[Min]) Min = i + 1;} ; N9 R2 { V% @) W4 T ( u$ L1 h1 D1 ?! ]else { J H1 k5 u" A7 s" J
1 _1 u# N, O G0 G
if (w[i+1] > w[Max]) Max = i + 1;; c+ @0 k0 B* B, F t
* V' U J! N, Xif (w < w[Min]) Min = i;} # p8 _2 O+ ?2 j1 w U' l( M U$ m) c% A6 d4 O9 `: n6 U
}2 m- f ^" _: X$ j9 @" ]. i
( J. Z. k$ A5 L* c l) U, Creturn true; w6 c; K, j3 D v8 T3 d
, b9 ~* g7 \* ~) l Z! y}+ _8 J2 {1 N; Y0 L
$ ^4 a/ x) `8 S' e
练习. {7 @) @% i/ ^2 j6 }( K1 ^; m
% D( Z3 K- t4 e
1. 将例1 4 - 1的分而治之算法扩充到n> 1个硬币的情形。需要进行多少次重量的比较?' }& R" I) _$ ^5 E+ D5 Z8 @
1 t( T7 ~0 s. y+ \! D2. 考虑例1 4 - 1的伪币问题。假设把条件“伪币比真币轻”改为“伪币与真币的重量不同”,同样假定袋中有n 个硬币。% l$ m; M# g3 E0 j
1 j; ?% |0 W2 f% E7 H& f: F1) 给出相应分而治之算法的形式化描述,该算法可输出信息“不存在伪币”或找出伪币。算法应递归地将大的问题划分成两个较小的问题。需要多少次比较才能找到伪币(如果存在伪币)?1 k0 J# A$ j' [6 A. i
2 J) E( T3 G+ P( m. a2) 重复1) ,但把大问题划分为三个较小问题。 0 z1 f# ^' H6 Y3 r 8 P0 l1 S* n2 z. K6 u4 ^5 j+ i3. 1) 编写一个C++ 程序,实现例1 4 - 2中寻找n 个元素中最大值和最小值的两种方案。使用递归来完成分而治之方案。 ) H! W. u8 p+ m3 A u7 l, O ) s0 K, g8 J7 ?/ g) _, I: {7 D2) 程序2 - 2 6和2 - 2 7是另外两个寻找n 个元素中最大值和最小值的代码。试分别计算出每段程序所需要的最少和最大比较次数。 5 E; k: p! j+ m# T, s" o" t * s# u8 B% R* P2 Q+ m8 ]1 e l3) 在n 分别等于1 0 0,1 0 0 0或10 000的情况下,比较1)、2)中的程序和程序1 4 - 1的运行时间。对于程序2 - 2 7,使用平均时间和最坏情况下的时间。1)中的程序和程序2 - 2 6应具有相同的平均时间和最坏情况下的时间。0 Q* }9 b. l1 Q1 Y9 n
' j6 \7 I9 s7 Q
4) 注意到如果比较操作的开销不是很高,分而治之算法在最坏情况下不会比其他算法优越,为什么?它的平均时间优于程序2 - 2 7吗?为什么?6 h: z" ?0 f1 x! B5 w" a
/ E, {; M# `% _+ Z9 T+ p$ b) d& [6 T
4. 证明直接运用公式(1 4 -2)~(1 4 - 5)得出结果的矩阵乘法的分而治之算法的复杂性为(n3 )。因此相应的分而治之程序将比程序2 - 2 4要慢。 ! w! M: T& e' p6 @ 6 Z; K' t" z: q0 y5. 用迭代的方法来证明公式(1 4 - 6)的递归值为(n l og27)。7 V, w0 D% [. c# k8 ?/ w3 G, u
. x9 v% v! y+ q% n% I0 J: T
*6. 编写S t r a s s e n矩阵乘法程序。利用不同的k 值(见公式(1 4 - 6))进行实验,以确定k 为何值时程序性能最佳。比较程序及程序2 - 2 4的运行时间。可取n 为2的幂来进行比较。/ v) y Y, [, J1 [4 L
7 z% @1 [' D; M+ U' Q7. 当n 不是2的幂时,可以通过增加矩阵的行和列来得到一个大小为2的幂的矩阵。假设使用最少的行数和列数将矩阵扩充为m 阶矩阵,其中m 为2的幂。1 x9 }% h E: U5 l3 B! E