- 论坛徽章:
- 0
|
Exercise 3.63. 答案
- 先分析使用局部变量 guesses 的版本:
- (define (sqrt-stream x)
- (define guesses
- (cons-stream 1.0
- (stream-map (lambda (guess)
- (sqrt-improve guess x))
- guess)))
- guess)
复制代码
使用如下缩写:
stream-cdr: scdr
stream-map: smap
sqrt-improve: f
(lambda () ...) : ()->...
设 sqrt-stream 产生的流序列为 r0, r1, r2, ...
设 R0 = (sqrt-stream 2)。对 R0 不断 scdr,可以得到一系列的 pairs,这些 pairs 都只有
两项。第一项是个实数 r_i,第二项是个无参函数。把这些 pairs 记为 R1, R2, R3, ...
如果要访问 r_i,则需求出 R_i,然后 car 出其第一项。car 是平凡的,重点在于如何求出 R_i。
R0 = (r0, ()-> (smap f R0))
假如现在要求 R1, 则需要对 (cdr R0) 进行 force。即 (scdr R0),也就是 (smap f R0)。
smap 把 R0 的首项 r0 car 出来,f 作用后,cons-stream 到 (smap f (scdr R0)) 上。得到:
R1 = (r1, ()-> (smap f (scdr R0)))。
要注意 cons-stream 是个宏,它是特殊的,(scdr R0) 并没有被马上求值。
好,现在我们求 R2, 即 (smap f (scdr R0))。
smap 是个函数,所以先对 (scdr R0) 进行求值。先不管复杂度,(scdr R0) 是求值过的,我们知道
结果是 R1,值为 (r1, ()-> (smap f (scdr R0)))。因此 (smap f (scdr R0)) 就是
(smap f R1)。
从 R1 中提出出 r1 并把 f 作用上去得到 r2。然后把 r2 cons-stream 到 (smap f (scdr R1)))
上得到
R2 = (r2, ()-> (smap f (scdr R1)))。
接着求 R3= (smap f (scdr R1)) = (smap f R2) = (r3, ()-> (smap f (scdr R2)))
......
不难看出,模式为: R[j] = (r[j], ()-> (smap f (scdr R[j-1])))
- 现在来考虑计算量,以从 R[j-1] 求值 R[j] 为例。
不带记忆的情形:
R[j] = (smap f (scdr R[j-1])) = (smap f (smap f (scdr R[j-2])))
= ... = (smap f (smap f (smap f ... (smap f R[0]))))
或
R[j] = (scdr R[j-1]) = (scdr (scdr R[j-2])) = ... = (scdr^j, R[0])
每执行一次 scdr,都进行了一次 force,f 也被执行一次。如果认为耗时的主要在 f,则从 R[j-1]
计算 R[j] 要执行 f j 次。这显然不合算,因为从 r[j-1] 计算 r[j] 只需要执行 f 一次。
如果 (define S2 (sqrt-stream 2)),现在考虑 (stream-ref S2 n) 的计算量。为访问 r[0],
必须计算出 R[0], R[1], ..., R[n],所以 f 被执行了 1+2+...+n 次。复杂度为 O(n^2).
更糟糕的是,以后再次访问 S2 中的第 n 项,这些计算全会重新执行。
如果是带记忆的版本,delay 被首次 force 时,会把计算结果存放在一个局部变量中。当 delay
被再次 force 时,它简单地返回已保存的值。此时从 R[j-1] 求值 R[j] 将会是
R[j] = (r[j], ()-> (smap f (scdr R[j-1])))
R[j] = (scdr R[j-1]) = (smap f (scdr R[j-2]))
由于 (scdr R[j-2]) 在计算 R[j-1] 时已经求值过,所以直接返回保存的值
(r[j-1], ()-> (smap f (scdr R[j-2])))
所以
R[j] = (smap f (r[j-1], ()-> (smap f (scdr R[j-2]))))
= (f(r[j-1]), ()-> (smap f (scdr (r[j-1], ()->(smap f (scdr R[j-2]))))))
= (r[j], ()-> (smap f (scdr R[j-1])))
这样只需要执行一次 f,两次 force。两次 force 中,一次是新的,一次是 force 过的。
若 (define S2 (sqrt-stream 2))。则首次 (stream-ref S2 n) 时,需要执行 f n 次。
若再次执行 (stream-ref S2 n),则无需执行 f,但仍需要 O(n) 次 force.
- 最后看一下无局部变量 guesses 的版本:
- (define (sqrt-stream x)
- (cons-stream 1.0
- (stream-map (lambda (guess)
- (sqrt-improve guess x))
- (sqrt-stream x))))
复制代码
这个版本有缺陷。stream-map 调用中使用 (sqrt-stream x) 会生成一个“新”的平方根序列,
从而无法利用 delay 的记忆机制保存的值。在首次求值时,效率上等同于没有记忆。
比如:(define x (sqrt-stream 2)),则首次 (stream-ref x n) 时,效率与带 guesses
版本使用无记忆 delay 时相当,即 O(n^2)。 但再次访问 x 中已求值的项时,delay 的记忆机
制将发挥作用,从而使访问量降为 O(n).
[ 本帖最后由 win_hate 于 2009-2-1 11:02 编辑 ] |
|