. W4 T: K! z' z; k9 N8 h! i 设第 1 次分裂之前的个数为 x 0 、第 1 次分裂之后的个数为 x 1 、第 2 次分裂之后的个数为 x 2 、……第 15 次分裂之后的个数为 x 15 ,则有 . G* j5 F' a, c2 ?
9 Y5 ^ t; ]+ w# |/ C% z5 X
x 14 =x 15 /2 、 x 13 =x 14 /2 、…… x n-1 =x n /2 (n ≥ 1) ; r; X0 V% ?2 a# W4 q. J5 G * h% \4 z; L7 K* f" e# d1 g+ l, J 因为第 15 次分裂之后的个数 x 15 是已知的,如果定义迭代变量为 x ,则可以将上面的倒推公式转换成如下的迭代公式: 0 Q& S4 H6 c) S7 i c. R& D; U2 q7 C' z: c- [ m! h+ J- N
x=x/2 ( x 的初值为第 15 次分裂之后的个数 2^20 ) " ]; l( T- ]6 A. {" V* V1 l2 G K2 i* `9 I0 V1 S4 ?3 j3 a) u
让这个迭代公式重复执行 15 次,就可以倒推出第 1 次分裂之前的阿米巴个数。因为所需的迭代次数是个确定的值,我们可以使用一个固定次数的循环来实现对迭代过程的控制。参考程序如下: 9 a& M) }# V. P% T# V: [ K
5 q0 U8 ]; w' z9 ?* ^- j5 X' i' x( M0 M
cls 4 H+ n8 P* W: W' R, L L9 @" \% y0 }, r {+ F. i- s3 I x=2^20 - U% H( Y$ s. M* k. }8 Z7 c / n; E; z! W H ^ for i=1 to 15 2 ^0 G" @7 Q8 B% E6 Q
& {8 N l2 z( c x=x/2 8 r+ g8 \' L' F/ S) M; ]1 I, r9 m ; K3 t Z) n7 c% _4 X9 e/ G next i . V% x0 H: @* I/ X# a8 u& }- |3 ~* \/ h+ ~% I. w3 h" g% [, E/ A
print x * q- A* s' ^( W( z9 H2 c- r E7 ~1 G ; | E5 L2 T) j9 i( ^( V end* C4 g+ G8 R! n% @
' j1 H z* `' R, D( d ps:java中幂的算法是Math.pow(2, 20);返回double,稍微注意一下$ o S4 k" b7 P7 R, y
+ W* Y, j2 t$ d$ d3 n% y3 T! _
例 3 : 验证谷角猜想。日本数学家谷角静夫在研究自然数时发现了一个奇怪现象:对于任意一个自然数 n ,若 n 为偶数,则将其除以 2 ;若 n 为奇数,则将其乘以 3 ,然后再加 1 。如此经过有限次运算后,总可以得到自然数 1 。人们把谷角静夫的这一发现叫做“谷角猜想”。 : {- s$ W: G" g4 R$ S, O9 v
* n0 A* k, e: b& R6 x9 Q+ m
要求:编写一个程序,由键盘输入一个自然数 n ,把 n 经过有限次运算后,最终变成自然数 1 的全过程打印出来。 8 v1 L/ `8 W6 i5 \/ @4 l% M1 \. v; k x7 Z% g5 ]
分析: 定义迭代变量为 n ,按照谷角猜想的内容,可以得到两种情况下的迭代关系式:当 n 为偶数时, n=n/2 ;当 n 为奇数时, n=n*3+1 。用 QBASIC 语言把它描述出来就是: 2 M: z! _4 {# K4 J
0 R3 g' W* {0 f
if n 为偶数 then # g5 F: [% P8 b' T( U 3 s) G( a; N$ G/ o V' b n=n/2 * Y& L$ s5 t' x. L1 V* Y' L. y7 N+ \- F0 a d) E% g
else ! h+ z7 f4 n& j- {3 }% v- B 1 A6 i! K) s* E% e& l7 U% |, \ n=n*3+1 + o$ _0 b. ~2 ~* z& Y
" n; {" E9 N: _ j6 R end if I3 u3 a8 m4 U/ C8 q7 Z. U ! ~$ P; ~6 M! t' ^) i* k5 a- A 这就是需要计算机重复执行的迭代过程。这个迭代过程需要重复执行多少次,才能使迭代变量 n 最终变成自然数 1 ,这是我们无法计算出来的。因此,还需进一步确定用来结束迭代过程的条件。仔细分析题目要求,不难看出,对任意给定的一个自然数 n ,只要经过有限次运算后,能够得到自然数 1 ,就已经完成了验证工作。因此,用来结束迭代过程的条件可以定义为: n=1 。参考程序如下: ) O2 g5 E! v. Y: a7 b. o( U9 R" l6 j* F; W; `# x
cls " a, [' O9 Z: L 7 v/ f5 D. h6 {# I* | input "Please input n=";n ) l( ]5 i& t- D a- j
$ x" r5 x7 X+ P% L V8 v
do until n=1 ' y: j0 M. K. g
/ w% j% S# v/ K7 O( B# l# L2 _ if n mod 2=0 then $ f' `2 s, T6 I7 s. n) P+ ~& i5 l% n% y
rem 如果 n 为偶数,则调用迭代公式 n=n/2 P" k2 N# I$ J' K5 l& Q e5 V9 A6 n
n=n/2 " k; J* l' j# _* d5 R( m3 p, @ . ]' P/ D2 a7 }; l5 b0 U print "—";n; - K' J9 H) l( L) V/ r$ V- j3 Z- k- E- z* E0 v
else 7 I+ p' X1 t% w! _; t0 c b- c
8 D6 D" K, M. Z) q print "—";n; ' K1 }6 n- B3 y! k. a: u$ Y0 I3 w7 D
end if 8 W, P) H+ ^/ ` 3 N8 a9 o q3 R7 J) I! c loop 3 T3 K- q0 j7 D2 W2 K5 P' t O# j0 k4 J 8 j2 Y" V/ D- ?, {& ] end |6 ~, ?: I5 f9 S$ C4 k $ B" e6 F9 F, l- d 迭代法 0 e/ x. T7 J! `# }' V1 q
. o; i0 U4 [- V5 u0 ~ 迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行: , s8 w$ Q( R- t ) c0 t% m$ p( H$ W0 H& B9 I (1) 选一个方程的近似根,赋给变量x0; ) s, N, B* g6 }6 P
8 T4 F# M+ R5 }& t, U
(2) 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0; 3 b- `7 Z% ~& F/ v& D# U) h
4 w& Z9 K/ }- m& ^) ~! A8 m& V (3) 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。 - Y D/ E2 a. u" Q# f
9 L- Y F( b ~+ z! y1 `! b7 J! a 若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为: 6 p! p5 w G ^. _
9 {- \$ m5 k, H4 n5 f. S- V/ | 【算法】迭代法求方程的根 3 k, S1 Z t- P1 A! J. b& L
3 j* y! ]' {; x. W* `3 |9 o* C
{ x0=初始近似根; 5 Q) T; Z4 z$ D% u! U6 i1 `! u
! e3 s# B9 V/ w do { ! e& H% o. _& |% H+ P8 Y: m. H: q- y. E. b! R6 q
x1=x0; 4 S. y! p+ t- }% z8 J7 e9 a
. a# F7 ?6 n9 g ?8 b- x. K' r
x0=g(x1); /*按特定的方程计算新的近似根*/ & B3 I" D9 E% _+ b/ E1 m; ] 5 w( _0 \) a& E" S } while ( fabs(x0-x1)>Epsilon); ; w n; L4 D1 U: d& q7 Z( S
# {) h* B8 Z5 _% h7 s+ M4 m6 s' L) b printf(“方程的近似根是%f\n”,x0); 4 t, M. z K! x! O
+ ~0 V4 V0 i/ n3 N
} - G2 m0 g( l; @' @ ) @; P' p( k, z% _ 迭代算法也常用于求方程组的根,令 2 n ~ g: X1 w 8 o' `3 u; T0 j% {' J X=(x0,x1,…,xn-1) 0 q! m+ b8 T9 w; m/ E0 b
8 B; ]+ j& x/ A5 l. o 设方程组为: 6 z- i7 w8 z& d3 l+ w
/ C5 M+ v$ D. L/ H2 W xi=gi(X) (I=0,1,…,n-1) ! X- z. m1 J |% u
$ Y% Z9 ]0 c8 h4 w- F$ o 则求方程组根的迭代算法可描述如下: 4 B" c* O r$ m) |3 u0 U) X, v. {6 r; K2 k% k
【算法】迭代法求方程组的根 - W) s2 \! p3 i) h3 h/ K8 W& A: W; z8 H( G+ Z% a; V
{ for (i=0;i - T( `8 O/ V# n% H2 C8 ]' E7 i3 H: R3 }' ^! @2 u
x=初始近似根; ! W9 X$ M6 Q. o! D& n$ W+ ?$ ]. {% U, m9 n, M' H* e
do { 4 F! [4 T- H! w& y1 P1 W$ z" V
* q" C! J9 [5 O; S" O for (i=0;i 8 K! \4 d) ~1 D" I
; s6 B" H5 s( i/ u* n$ k
y=x; 2 h! B. U, X! b- w W
1 `8 S% U0 _/ h, Y4 G1 B9 K for (i=0;i / v; T3 b. E0 B: u& C z5 W4 e" }4 y
x=gi(X); : U' e6 T0 ?2 L) ^ 4 F6 W' ]+ l1 w8 O3 G+ B% [# e4 z$ _ for (delta=0.0,i=0;i 3 \3 h1 o: {: w9 t% x 1 I5 U- k ~9 ]! z* K3 i3 d, M, G; h if (fabs(y-x)>delta) delta=fabs(y-x); ' C T7 V& d: }% Z8 m+ t, J- G9 ?! k) `
} while (delta>Epsilon); , m8 r* x1 A* `$ \/ P/ N S% U6 `3 m0 _/ l# [" G7 D
for (i=0;i - O$ M! ~, L$ O" b/ ]' K, U
1 q5 g8 ~: U. s4 |* U. }$ v1 r" ?
printf(“变量x[%d]的近似根是 %f”,I,x); C) s0 R$ r+ r! y. X. H/ X% Z. Z
$ N4 |$ f0 W: ^ printf(“\n”); + n7 ^1 Q% ~* S+ T
3 ~1 ~3 a/ R+ K" A2 I
} 1 `. I: ?0 A; X8 p# [* {' R' T " h" |' }* ?9 o: |3 \ 具体使用迭代法求根时应注意以下两种可能发生的情况: * @7 @, w! N0 Y* A3 {! C
# R g6 a$ v1 A, K# Z: C2 K6 r3 N
(1) 如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制; + S; u% I9 k9 q! u% r1 b
+ L/ \. ]5 C, J
(2) 方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。 2 I: n- ]$ |( y: D& Q& ^# j2 J+ R& ~. Z3 j& p1 n
递归 3 C9 I& u/ ?- q$ a
6 |/ R: p, y4 Y- H7 z2 ?% q
递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。 r; u. I6 i( v9 |9 T 6 p. U" ~' T$ W: X x 能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。 ) o2 N2 L4 V( v% V# h4 S# i % T& r& n) J9 [9 y* \# U$ a- l( s 【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。 2 g, V Q: _! Z: F- F+ c ) w+ p( G2 A3 Q' ?% [ 斐波那契数列为:0、1、1、2、3、……,即: * v$ q" L: v, x2 q" A ) p! O0 X7 }5 ~$ N# i$ w: |$ k. e fib(0)=0; 1 F# N/ P7 d! I! @
8 ]" j) a8 w) c5 V) a1 o% | fib(1)=1; $ O' G8 _% ?9 X; |6 Q+ X
3 r5 ~" ^0 ?& D% ]* U/ x+ ~& o
fib(n)=fib(n-1)+fib(n-2) (当n>1时)。 2 u0 }8 u* {# A ; I2 M( m5 x0 b1 t+ j0 n) d, X/ K 写成递归函数有: # V# `/ y5 [0 Q$ c
4 [1 f, j6 z9 [* S# f8 M
int fib(int n) 1 W) I( U, O7 @6 l- w, \ " v$ B% n4 B$ D. J: i0 _9 n { if (n==0) return 0; + \2 v, k! [, G) N + k4 A" x+ X6 Z! Y" o if (n==1) return 1; 4 m" b8 K2 j2 K" k9 [! s$ i- x 3 m6 q) }3 x8 Q# ] if (n>1) return fib(n-1)+fib(n-2); , w9 [* E$ c8 ?/ f+ Q: V7 z
) D& v' i' N$ L6 [4 T } ) C+ d, Q2 w# ~7 x- n& B; a! W2 r5 D C: v a% R, Z
递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n- 2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。 6 x+ D* j3 N7 H/ Q: | 6 e0 M) G" U, k 在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。 : v2 n3 z; z: k& b, ]9 |: W6 P! z: q& u( H, E4 b
在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。 . ]2 P+ _' E! M6 I7 G9 M6 \
5 `3 |/ h1 |* A
由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。 $ w i: V1 z& f
$ @7 A" q9 v ` i- r3 O i=0; 8 N% `3 O& H4 o2 N3 R8 m
: k% P- Q7 X* K) `, ]+ M9 b1 n
While (i>=0) , u7 c' y6 S0 j: ]6 a ! s$ D# i3 j# |2 S( [: I" G$ K7 z0 x { f=twv.; 7 E0 J ]0 ~, k , j6 v# c8 \ b: X tw=twv.tw; % n+ v O, a- ]2 A' u
4 d, @) p. t& _8 b( A+ w8 }
tv=twv.tv; . j" ]) ~5 h2 P* B/ N( Q2 H8 v2 f$ s; I- R" d4 r; @5 ?' O
switch(f) ( v8 B- Y5 J1 B: Y
, F: t' y& |3 g
{ case 1: twv.++; & {: |. @, {# R4 ]% ^, W* p* l1 p
% L1 b; Q9 |8 ~9 {# m! d if (tw+a.weight<=limitW) j" P9 l, t' \- g$ t- P
$ x- P, u& N$ f S. W7 D
if (i , [% V, O3 E+ ]
- s8 g/ I: h4 k9 S% R$ e9 y' u) G
{ next(i+1,tw+a.weight,tv); ) x7 F1 R; R/ n6 M; Q3 @2 }0 r
7 } n g$ R; D% ? P
i++; ) d' I$ k- }6 T% c P% N2 r) E
4 @0 H E* V3 i# y s) `* U$ z" u } 6 j! w l \0 q0 R. w8 P' w) C9 d4 c2 X |
else ( G5 l% ]5 m! w
5 Q- }) `: ?* ]; j2 x& k3 y { maxv=tv; 8 R0 F0 B: m1 p5 v: m- p4 [5 t1 b, I( }1 ?! ]( v& Q% d) @9 h
for (k=0;k 0 T9 i) A" C( G, p
( x7 k, P, b' z) j+ K. P
cop[k]=twv[k].!=0; 1 s7 p9 I; c& q4 t; d% u7 G
$ t, H7 p1 a- E! T4 y `
} 5 T' `4 Y" s K
6 g- K5 P; W5 Z' X* I9 X: E' V break; 1 ~- P2 F; V/ ^/ `; a5 \4 _4 [
( _. v, O2 A' v: w2 W
case 0: i--; . O' C* O- [* N7 k. I6 k
7 u5 z$ Z2 p: R* X! r. Y0 x7 ]) u5 `
break; : C9 `7 G2 q2 C+ q a2 @% {9 {8 A8 ~( S# d0 z' N4 u
default: twv.=0; 2 m" N7 i# M5 z3 U% H4 \8 B8 M9 s* a3 k' F) X5 |
if (tv-a.value>maxv) 2 M: o& n, Z0 T) ` + v8 @: c4 H6 a4 A- f" E if (i / \1 w, b# z$ W2 p
1 W. m2 e, _" b
{ next(i+1,tw,tv-a.value); ' e6 C1 z% z- ]% K/ v$ J8 f. k
2 y# l. @+ ]7 g1 j; @
i++; ~+ k: w4 }8 B' c3 h* P: Y
; ~& R g, Q- f- U3 s! Y } 4 y" E& G9 ]8 K- H) _* Y+ C) c: ~; f# B# S0 _- P: C
else / r6 w0 z( Z2 n' z% J5 Y; [ " r4 k4 |3 B' E' D i { maxv=tv-a.value; ( b2 m# m, z7 C/ U8 a
! D) g2 p+ l: r for (k=0;k # e3 {" Y. s+ B
. ^4 u; ]( k; @1 `
cop[k]=twv[k].!=0; - ], L/ d+ o/ t2 d & n8 R; o2 p! n1 m9 C A/ @/ Q X } 2 Z0 Q8 V! [5 X) y: a# F
) \" H% ~: i* m$ |. R break; 1 r' i F- K3 B5 D! C9 o! Z $ L% x' ~ O$ e: ^. r } * B/ g; L) i4 [* u+ E& K& A: I1 N ' q$ J, Y/ ]2 F+ g; ]4 m } & b: ~* B1 l; O' I% s1 p, B 1 G, G) s1 I. h% q return maxv; $ Z' G G& i7 H! S1 c; ]3 |+ s4 [8 u
+ ~ ^# v1 r: y+ P
} * i( `9 Q5 V8 I' ]
2 K/ Z4 d/ ]6 P% n9 U4 r% Y f( z void main() + ?4 v' X$ v/ r3 {* e/ Q! i( p7 Y+ s3 c
{ double maxv; : D9 D4 J! h! G
7 v! ~9 S4 v8 A7 F! z! ]* H printf(“输入物品种数\n”); % `# [! \. N1 {2 q& l4 N
1 N, W" G$ `; c6 Y3 Q6 \+ r6 ~: ?1 B
scanf((“%d”,&n); 7 H4 e1 Y( X4 t- r6 `& E0 y9 ] , C1 w1 l6 }6 e" M' C printf(“输入限制重量\n”); ! S* ?: [4 l+ L0 z* x ' }7 [# P2 A" @2 g: W scanf(“%1f”,&limitW); 4 \- w0 v& Q$ Y) o6 ]- {7 J; l
' A3 a6 ^1 k7 k
printf(“输入各物品的重量和价值\n”); - M" S* G8 b. D
( {3 V) Q$ F; Q B$ p for (k=0;k 3 ^: J/ }* U: X6 O4 t( Z& U
* j3 {) \9 Q: X4 v3 U' {! `9 M- v; y }
scanf(“%1f%1f”,&a[k].weight,&a[k].value); 9 r. `" \' Q3 j& Z2 l+ z2 U5 t& h ?* f; @$ h1 q
maxv=find(a,n); ' R: w. t9 y C% s) O
; W! E% s7 ^, l printf(“\n选中的物品为\n”); 8 A8 h) w [2 r2 [. B! J# g" w( q! b9 y, y) f9 T0 D+ Z1 J
for (k=0;k 8 p7 q9 ]9 z' R# m, l, W/ m' Z( i) y3 {( {( s; A: P4 S
if (option[k]) printf(“%4d”,k+1); 2 Y0 m4 F3 ~, L0 X E, w' K0 h [- H4 @0 V: }2 t
printf(“\n总价值为%.2f\n”,maxv); - L; j: b6 p! K5 l9 m
- M0 G5 u1 r. N7 O! u5 M } 8 H: L0 w+ W( t / n/ W+ o9 C# X! n 递归的基本概念和特点 0 w( ]4 K; Y, p/ B! Y
2 F6 s$ n" {& k, x
程序调用自身的编程技巧称为递归( recursion)。 . U: h& }1 |, \/ n3 B $ @; Z. w) z4 y3 S6 ?$ p 一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。 ' ^* Y+ e. c7 U9 |) K $ ]4 ~. n+ K8 z! k 一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。 5 H; w; e X4 o* O r) ?# H 4 R& W9 E* q4 h6 \8 k 注意: ! W6 X8 K0 V) p% \( q+ J ; Z5 k) x" @( `$ m, [' B (1) 递归就是在过程或函数里调用自身; & h5 E7 f- R; e( ] Y
2 O! p, h: K9 L4 w (2) 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。