CG@CPPBLOG

/*=========================================*/
随笔 - 76, 文章 - 39, 评论 - 137, 引用 - 0
数据加载中……

我的SICP习题答案(1.20)

1.20
下面用 % 表示过程 remainder,那么正则序的过程如下:
 (gcd 206 40)
 (if (
= 40 0206 (gcd 40 (% 206 40)))
 (gcd 
40 (% 206 40))
 (if (
= (% 206 400;<##  1> [6]
     40 
     (gcd (% 
206 40) (% 40 (% 206 40))
 (gcd (% 
206 40) (% 40 (% 206 40)))
 (if (
= (% 40 (% 206 40)) 0)   ;<##  2> [4]
     (% 206 40)
     (gcd (% 
40 (% 206 40)) (% (% 206 40) (% 40 (% 206 40)))))
 (gcd (% 
40 (% 206 40)) (% (% 206 40) (% 40 (% 206 40))))
 (if (
= (% (% 206 40) (% 40 (% 206 40))) 0;<## 4>[2]
     (% 40 (% 206 40))
     (gcd (% (% 
206 40) (% 40 (% 206 40)))
          (% (% 
40 (% 206 40)) (% (% 206 40) (% 40 (% 206 40))))))
 (gcd (% (% 
206 40) (% 40 (% 206 40)))
      (% (% 
40 (% 206 40)) (% (% 206 40) (% 40 (% 206 40))))))
 (if (
= (% (% 40 (% 206 40)) (% (% 206 40) (% 40 (% 206 40))))) 0;<## 7>[0]
     (% (% 206 40) (% 40 (% 206 40)))
     (gcd ...)
 (% (% 
206 40) (% 40 (% 206 40)))  ;<## 4>[2]

可以看出需要调用 remainder 共 1+2+4+7+4 = 18 次。

应用序计算过程如下:
(gcd 206 40)
(if (
= 40 0206 (gcd 40 (% 206 40))) ;<##>
(gcd 40 6)
(if (
= 6 040 (gcd 6 (% 40 6)))      ;<##>
(gcd 6 4)
(if (
= 4 06 (gcd 4 (% 6 4)))        ;<##>
(gcd 4 2)
(if (
= 2 04 (gcd 2 (% 4 2)))        ;<##>
(gcd 2 0)
(if (
= 0 02 (gcd 0 (% 2 0)))
2

可以看出共需 4 次。


posted @ 2008-03-27 21:59 cuigang 阅读(1014) | 评论 (2)编辑 收藏

我的SICP习题答案(1.16~1.19)

1.16
(define (fast-expt x n)
  (fast-expt-iter x n 
1))

(define (fast-expt-iter x n a)
  (cond ((
= n 0) a)
        ((even? n) (fast-expt-iter (square x) 
                                   (/ n 
2)
                                   a))
        (else (fast-expt-iter x 
                              (- n 
1)
                              (* a x)))))

1.17
(define (multi x y)
  (if (
= y 0)
      
0
      (+ x (multi x (- y 
1)))))

(define (fast-multi x y)
  (cond ((
= y 00)
        ((even? y) (double (fast-multi x
                               (half y))))
        (else (+ x (fast-multi x (- y 
1))))))

(define (double x) (+ x x))
(define (half y) (/ y 
2))

1.18
(define (double x) (+ x x))
(define (half y) (/ y 
2))

(define (fast-multi x y)
  (fast-multi-iter x y 
0))

(define (fast-multi-iter x y p)
  (cond ((
= y 0) p)
        ((even? y) (fast-multi-iter (double x)
                                    (half y)
                                    p))
        (else (fast-multi-iter x (- y 
1) (+ p x)))))

1.19
;T(pq):(a,b)=>(bq+aq+ap, bp+aq)
;
T(pq):(bq+aq+ap, bp+aq)=>((bp+aq)q + (bq+aq+ap)q + (bq+aq+ap)p,
;
                          (bp+aq)p + (bq+aq+ap)q)
;
      => (2bpq+2aqq+bqq+2apq+app, bpp+2apq+bqq+aqq)
;
      => (b(2pq+qq)+a(2pq+qq)+a(qq+pp), b(qq+pp)+a(2pq+qq))
;
q' = 2pq+qq
;
p' = qq+pp
;
(define (fib n)
  (fib-iter 
1 0 0 1 n))

(define (fib-iter a b p q count)
  (cond ((
= count 0) b)
        ((even? count) 
         (fib-iter a
                   b
                   (+ (* p p) (* q q))
                   (+ (* 
2 p q ) (* q q))
                   (/ count 
2)))
        (else (fib-iter (+ (* b q) (* a q) (* a p))
                        (+ (* b p) (* a q))
                        p
                        q
                        (- count 
1)))))


posted @ 2008-03-27 00:53 cuigang 阅读(822) | 评论 (1)编辑 收藏

我的SICP习题答案(1.14~1.15)

1.14
计算过程的树如下:


很容易看出,计算过程的空间需求,也就是树的深度,取决于最左边的子树,即(n 1),它的深度是n+6,O(n).

然后对于计算步数,也就是树的节点数,我们知道对于一个二叉树,树的节点数 = 左子树节点数 + 右子树节点数 + 1.
先来看 (n 1) 子树,设它的节点数是f(n), 而且总有,非叶节点左子树节点数为1
当 n=1,f(1) = 3
   n>1, f(n) = 1 + f(n-1) + 1 = f(n-1) + 2 = f(n-2) + 2*2
             = f(n-(n-1)) + 2*(n-1) = 2n + 1
             = O(n)

再来看 (n 2) 子树,设它的节点数 g(n), 设 ┌ n/5 ┐ = A
g(n) = f(n) + g(n-5) + 1 = f(n) + f(n-5) + g(n-5*2) + 2
     = f(n) + ... + f(n-5*(A-1)) + g(n-5*A) + 2A
     = O(n^2)

依此类推,可以得出结论 (n 5) 的计算步数增长的阶 为 O(n^5)

1.15
a) 12.15 连除 5次 3 小于 0.1 ,所以是 5次
b) 可以看出每调用一次 p 过程,需要递归1次 sine ,空间加1,计算步数加2,关键是p的次数:
   对于a,调用次数t,那么 a*3^(-t) < 0.1 , 即 10a < 3^t ==> lg(10a)/lg3 < t,
   所以增长阶 空间和时间 都为 O(log a)



posted @ 2008-03-26 22:56 cuigang 阅读(1271) | 评论 (2)编辑 收藏

推荐 SICP

我为什么推荐 SICP?

向大家推荐 SICP,不知道有多少人看了,也不知道有多少人明白了,更不知道有多少人惊叹了。或者你根本不屑一顾,或者你看见 Lisp 那层层括号心生畏惧,又或者你了了一瞥,觉得没什么精彩之处。那我真的很失望。

 
我为什么要推荐SICP,而且为什么如此执着?这本不算厚的书带给我的观念,是从未有过的,是关乎于软件本质的。曾几何时,我觉得我看到了计算机编程书中没有的哲学观,但这一次我的梦破灭了,那些已经被写进书里差不多快 30 年了。
 
对于 SICP,我真正算看完的,恐怕只有第一章。我现在就来谈谈我的心得,以再次向你展现这本书的魔力。
 
第一章作为基础,作者并没有象后续章节写太多的软件思想,主要还是介绍 Scheme 语言,所以草草看去,没什么精辟之处。不过在第一章中,作者用了大量的篇幅来探讨数学问题,因为他想向你揭示程序设计中的核心哲学:抽象。而数学无疑是最好的例子。
 
了解数学史的人,应该知道整个数学史,就是一个不断抽象的历史。古希腊人将字母引入计算,使数学不再只是算术,而且具有表达抽象规则的能力。近代数学对函数和微积分的探求中,用 f(x) 替代了多项式表达式,函数更一般了,然后 n 维空间、复分析、映射、泛函,抽象代数、群论,等等等等,直到集合论,摧毁了数学的基石,使数学界再次陷入沉思。
 
构造程序的方法也是抽象。从最简单的元素开始,基本元素(自演算表达式,包括数字,字符串和布尔值),然后定义基本过程(基本运算符,四则运算和布尔运算),进一步,自定义标识符(如同代数),再自定义过程(函数),再将过程作为值参与运算(高阶过程)。一步步的抽象,形成了整个程序的结构。而我们编程,无非就是从现实世界抽象出模型,再将模型不断的提炼抽象,属性、方法、类、继承、层次、框架。
 
编程就是一个不断抽象的过程。我再次把作者在第一章末写下的结论抄在这里,作为最后的注脚。
 
“作为编程者,我们应该对这类可能性保持高度敏感,设法从中设别出程序中的基本抽象,基于它们去进一步构造,并推广它们以创建威力更强大的抽象。当然,这并不是说总应该采用尽可能抽象的方式去写程序,程序设计专家们知道如何根据工作中的情况,去选择合适的抽象层次。但是,能基于这种抽象去思考确实是最重要的,只有这样才可能在新的上下文中去应用它们。高阶过程的重要性,就在于我们能显式地用程序设计语言的要素去描述这些抽象,使我们能像操作其他计算元素一样去操作它们。”

posted @ 2008-03-18 21:57 cuigang 阅读(16036) | 评论 (15)编辑 收藏

我的SICP习题答案(1.11~1.13)

1.11
递归计算过程为
(define func-recu
  (lambda(n)
    (cond ((< n 
3) n)
          (else (+ (func-recu (- n 
1))
                   (* 
2 (func-recu (- n 2)))
                   (* 
3 (func-recu (- n 3))))))))
迭代计算过程为
(define func-iter
  (lambda(a b c n)
    (if (
= n 0)
        a
        (func-iter b c (+ (* 
3 a) (* 2 b) c) (- n 1)))))

(define (func n) (func-iter 
0 1 2 n))

1.12
中文版原题翻译有误,应为计算pascal三角中的元素。
;pas(n,k)
;
if (k=1) or (k=n), then pas(n,k) = 1
;
else pas(n,k) = pas(n-1,k-1) + pas(n-1,k)

(define pas (lambda(n k)
              (if (or (
= k 1) (= k n))
                  
1
                  (+ (pas (- n 
1) (- k 1))
                     (pas (- n 
1) k)))))

1.13
中文版原题翻译遗漏 提示 :ψ=(1-√5)/2
已知,φ^2 = φ + 1, 那么 φ^n = φ^(n-1) + φ^(n-2)

同理,
ψ^2 = ψ + 1, 那么 ψ^n = ψ^(n-1) + ψ^(n-2)
φ-ψ = (1+√5)/2 - (1-√5)/2 = √5

when n=0, Fib(0) = 0 =
(φ^0-ψ^0)/√5
when n=1, Fib(1) = 1 = √5/√5 = (φ-ψ)/√5
when n>2, Fib(n) = Fib(n-1) + Fib(n-2) = (φ^(n-1)-ψ^(n-1))/
√5 + (φ^(n-2)-ψ^(n-2))/√5
                 = ((
φ^(n-1)+(φ^(n-2))) - (ψ^(n-1)+ψ^(n-2)))/√5
                 = (φ^n - ψ^n)/√5

又 -1<
ψ < 0, 故 -0.5< -1/
√5< ψ^n/√5 < 1/√5 <0.5 , 而 φ^n/√5 = ψ^n/√5 + Fib(n)

可知 |
φ^n/√5 - Fib(n)| < 0.5, Fib(n)是最接近φ^n/√5的整数。


posted @ 2008-03-12 23:10 cuigang 阅读(1457) | 评论 (0)编辑 收藏

我的SICP习题答案(1.10)

首先,可以写出这个函数的函数式:

            0,   y = 0;
f(x,y) =    2y,  x = 0;
            2,   y = 1;
            f(x-1, f(x, y-1));

那么,对于 f(0,n), n>=0
当   n >= 1 时, f(0,n) = 2n ,
而当 n =  0 时, f(0,0) = 0 = 2*0, 也满足 2n ,
故 f(0,n) = 2n, n>=0.

对于f(1,n), n>=1
当 n > 1 时,有 f(1,n) = f(0, f(1, n-1)) = 2*f(1,n-1),
设 f(1,n) = 2^n
if   n = 1,    f(1,1)    = 2 = 2^1
when n > 1, if f(1,n-1)  = 2^(n-1)
then f(1,n) = 2*f(1,n-1) = 2*(2^(n-1)) = 2^n
故 f(1,n) = 2^n, n>0.

对于f(2,n), n>0
if n > 1 ,then f(2,n) = f(1, f(2, n-1)) = 2^f(2,n-1),
设 f(2,n) = 2^(2^(... (n 个 2)
if   n = 1, then f(2,1)  = 2
when n > 1, if f(2, n-1) = 2^(2^(...        (n-1)
then f(2,n) = 2^f(2,n-1) = 2^(2^(

这样我们对于 (A 1 10) = 2^10 = 1024, (A 2 4) = 2^(2^(2^2)) = 2^16 = 65536
而 (A 3 3) = (A 2 A(3 2)) = A(2 A(2 A(2 1))) = (A 2 4) = 2^16 = 65536

(f n) = (A 0 n) = 2n
(g n) = (A 1 n) = 2^n
(h n) = (A 2 n) = 2^(2^(...      (n个2)

---------------------------------------------
在网上可以找到关于 Ackermann 函数的讨论,主要是针对这个双递归如何用迭代来实现,Ackermann 函数是 德国数学家W.Ackermann 在1928年提出的。在 WikiPedia 英文版上可以搜索 Ackermann function 词条,有详细介绍,不过这个Ackermann function 略有不同。如下图:


posted @ 2008-03-11 23:33 cuigang 阅读(1464) | 评论 (0)编辑 收藏

我的SICP习题答案(1.9)

很显然,第一个是递归的,第二个是迭代的。

(+ 4 5)
(if (
= 4 05 (inc (+ (dec 45)))
(inc (+ 
3 5))
(inc (if (
= 3 05 (inc (+ 2 5))))
(inc (inc (if (
= 2 05 (inc (+ 1 5)))))
(inc (inc (inc (if (
= 1 05 (inc (+ 0 5))))))
(inc (inc (inc (inc (if (
= 0 05 (inc (+ (dec 05)))))))
(inc (inc (inc (inc 
5))))
(inc (inc (inc 
6)))
(inc (inc 
7))
(inc 
8)
9

(+ 
4 5)
(if (
= 4 05 (+ 3 6))
(if (
= 3 06 (+ 2 7))
(if (
= 2 07 (+ 1 8))
(if (
= 1 08 (+ 0 9))
(if (
= 0 09 (+ (dec 0) (inc 9)))
9


posted @ 2008-03-11 21:53 cuigang 阅读(1419) | 评论 (2)编辑 收藏

我的SICP习题答案(1.8)

采用1.7中的变化率为终止检测。

(define (cube-root x)
  (cube-root-iter x 
1.0 x))

(define (cube-root-iter last-guess guess x)
  (if (enuf? last-guess guess)
      guess
      (cube-root-iter guess (improve guess x) x)))

(define (enuf? x y)
  (< (/ (abs (- x y)) y) 
0.001))

(define improve (lambda (y x)
                  (/ (+ (/ x (* y y)) (* 
2 y)) 3)))

posted @ 2008-03-11 21:24 cuigang 阅读(1189) | 评论 (0)编辑 收藏

面向过程和面向对象程序的形式

目前为止,程序的表现形式是对过程的叙述,顺序、分支、循环结构是最基本的原子形式。而面向过程的分析和设计无疑是最自然的框架结构,它将过程形式的代码段再次迭代的以过程形式组合,形成整个程序。就像将句子连成段,将段连成章,将章连成篇,将篇连成书。这也是最符合阅读者思维的形式,整个程序就像一个内含超链接的文本小说,主体上是流畅的,符合连续思维的。
 
面向对象程序不能说是颠覆性改变,它的原子表现形式仍然是顺序、分支和循环。而由于依赖于过程性系统的装载,其整体的最外层仍然是一个过程性的函数。它与面向过程在其表现形式上的不同,仅仅存在于中间层。
 
面向对象程序的表现形式是词条式的,至少是传记体的,而不是编年史。你可以想象,一部小说,作者首先把所有的故事按照角色重新归类,再分割为一个个小故事,可以想象是这样的:

——传记式:
《张三传》
     张三,秦人,少年,虬髯黑脸,为人仗义。
     逸事一:若傍晚时访之,必留宿,夜必邀相饮。三十三年春,李四自华阴来,……
《李四传》
     李四,中原人,白脸壮年书生,十六岁举秀才。
     逸事一:其人好游,某年遇张三……

——词条式:
醉酒:     张三与李四饮酒,大醉……
举秀才:   李四,十六时……
张三其人: 秦人,少年,虬髯黑脸,为人仗义……
巧遇:     三十三年,李四巧遇张三……
仗义好客: 张三好客,若傍晚时访之,必留宿,夜必邀相饮……
 
这种表现形式是某种词条式的分裂,故事被不断的片段化,一个比较好的面向对象程序一般会有大量的细粒度对象,对象的属性和方法都不多,方法都很短。虽然这种表现形式能解决一些过程性叙述的不足,但无疑过分的碎片化会带来理解上困难,传记式尚好,词条式就很难了。这就是面向对象程序在形式上出现的弱点。分析设计时,需时时脑中回想整体结构,以防偏离。而阅读维护时,需要把这些片段慢慢织起来,连成一个故事。

----------------------------------------------
将SICP的注脚240记在这里:
 
对象模型对世界的近似在于将其分割为独立的片断,函数式模型则不是沿着对象间的边界去做模块化,当“对象”之间不共享的状态远远大于它们所共享的状态时,对象模型就特别好用。这种对象观点失效的一个地方就是量子力学,在那里,将物体看作独立的粒子就会导致悖论和混乱。将对象观点与函数式观点合并可能与程序设计的关系不大,而是与基本认识论有关的论题。

 

posted @ 2008-03-11 20:50 cuigang 阅读(1451) | 评论 (3)编辑 收藏

我的SICP习题答案(1.7)

对于正文中的 good-enough? 谓词,设所求 x 的真实平方根为 xt,那么我们的依据是当 guess 从 1 趋向与 xt 的平方差 小于 c(0.001) 时,guess 近似于 xt。实际当 xt^2 也就是 x 足够小时, guess 会 逐渐稳定在 √c 附近。从下面实验可以看出:

> (sqrt (square 0.1))
0.10032578510960607
> (sqrt (square 
0.05))
0.054237622808967656
> (sqrt (square 
0.01))
0.03230844833048122
> (sqrt (square 
0.005))
0.031515954454847304
> (sqrt (square 
0.001))
0.031260655525445276
> (sqrt (square 
0.0001))
0.03125010656242753
>
 
因为 guess^2 < c + x , 当 x < c 时,guess 就更多的依赖于 c 了。

对于特别大的数,由于浮点数在特别大时,离散性非常明显,相邻的两个数之间的差距会非常大,导致 |guess^2 - x| 始终 大于 c,计算便进入了 无限循环。

比如计算 (sqrt 1e300)

使用变化率改进后的代码如下:

(define (sqrt-new x)
  (sqrt-iter-new x 
1.0 x))

(define (sqrt-iter-new s1 s2 x)
  (if (enuf-new? s1 s2)
      s2
      (sqrt-iter-new s2 (improve s2 x) x)))

(define (enuf-new? s1 s2)
  (< (/ (abs (- s1 s2)) s1) 
0.001))

可以测算几个数,验证结果还是比较好的。

> (sqrt-new (square 1e150))
1.0000000000084744e+150
> (sqrt-new (square 
1e-150))
1.0000000000084744e-150


posted @ 2008-03-11 00:07 cuigang 阅读(1621) | 评论 (2)编辑 收藏

仅列出标题
共8页: 1 2 3 4 5 6 7 8