SICP,等我

TMD谁把list 翻译成“列表”的,明明是“序列”的意思嘛→_→

——Pope, 2012

本科的时候啃过SICP,好像只读完了前两章。现在忙了,反而觉得应该好好看看,连以前不入眼的练习也决定一道道过。

解释器是用的DrRacket,前身是DrScheme。不知道为啥改了这么个怪名字-_-!

; Exercise 1.2
(/ (+ 5
      4
      (- 2
         (- 3
            (+ 6
               (/ 4 5)))))
   (* 3
      (- 6 2)
      (- 2 7)))

让我惊奇的是DrScheme居然返回-37/150!后来才知道Scheme里有种类型叫rational numbers。

; Exercise 1.3
(define (single? lst)
  (null? (cdr lst)))

(define (minimum lst)
  (if (single? lst)
      (car lst)
      (min (car lst)
           (minimum (cdr lst)))))

(define (sum-of-larger a b c)
  (- (+ a b c)
     (minimum (list a b c))))
(sum-of-larger 2 4 5)

Scheme用起来不爽的原因:一是很多“脚手架”都得自己搭,比如,判断是否为单一元素(single?)、序列的尾(有“头(first)”,但是木有尾(tail));二是提供的“脚手架”不好用,比如,求序列的最大/最小值,参数居然不能是序列,非得把要比较的元素一个个传进去,像这样(min 1 2 3)(当然,你也可以(apply min '(1 2 3)),但毕竟要转一道弯)。

看看人家Ruby, [1, 2, 3].first, [1, 2, 3].last, [1, 2, 3].min,多会来事^_^

; Exercise 1.5
(define (p) (p))
(define (test x y)
  (if (= x 0)
      0
      y))

;(test 0 (p))

练习1.5 很有意思(和后面的1.6一样)考察你对正则序(normal order)和应用序(applicative order)的理解。按照正则序求值是“完全展开而后规约”,而目前解释器实际采用的是“先对参数求值,而后将其代人到表达式中的所有同一参数”的应用序求值。应用序的好处就是可以避免重复对同一参数求值。

不管过程如何,先代到DrScheme里跑一遍。结果死循环,栈溢出了。稍作分析即可知道,DrScheme采用的是应用序,会先对参数求值。这样,0还是0,但对函数调用(p)求值却会进入死循环,直到栈溢出。如果解释器采用了正则序就会立即返回0,因为采用了正则序的解释器会先把(test 0 (p))展开为

(if (= 0 0)
    0
    (p))

而if是预置函数,采用的是短路求值,即一旦判断为真就不会考虑else-子表达式。这样,采用了正则序的解释器安全返回0,结束求值过程。

但这并不是说正则序比应用序好,你总能针对某种策略做出假死的情况来。

; 1.1.7
(define (sqrt-iter guess x)
  (if (good-enough? guess x)
      guess
      (sqrt-iter (improve guess x)
                 x)))
(define (improve guess x)
  (average guess (/ x guess)))
(define (average x y)
  (/ (+ x y) 2))
(define (good-enough? guess x)
  (< (abs (- (square guess) x)) 0.001))
(define (square x) (* x x))
(define (sqrt x)
  (sqrt-iter 1.0 x))

(sqrt 9)

; Exercise 1.6
(define (new-if predicate then-clause else-clause)
  (cond (predicate then-clause)
        (else else-clause)))
(define (sqrt-iter2 guess x)
  (new-if (good-enough? guess x)
          guess
          (sqrt-iter2 (improve guess x)
                     x)))
(define (sqrt2 x)
  (sqrt-iter2 1.0 x))
;(sqrt2 9)

和练习1.5相似。不同的是,这里想强调一点,“管你是数据还是函数,众生平等,但有些函数比其他函数更平等”。这部分函数就是预置函数。虽然new-if功能上与if完全一样,但new-if毕竟是个普通函数,所有传入的参数都会先行求值(应用序嘛);if就不一样了,人家是大妈生的,只在判决出来了,才选择性地对子表达式求值,所以不会陷入无穷递归(在本题中)。

; Exercise 1.7
(define (sqrt-iter3 guess old-guess x)
  (if (good-enough2? guess old-guess x)
      guess
      (sqrt-iter3 (improve guess x)
                  guess
                  x)))
(define (good-enough2? guess old-guess x)
  (< (abs (- 1 (/ guess old-guess))) 0.01))
(define (sqrt3 x)
  (sqrt-iter3 1.0 x x))

(sqrt3 2)

; Exercise 1.8
(define (cbrt-iter guess old-guess x)
  (if (good-enough2? guess old-guess x)
      guess
      (cbrt-iter (cb-improve guess x)
                 guess
                 x)))
(define (cb-improve guess x)
  (/ (+ (/ x (square guess))
        (* 2 guess))
     3))
(define (cbrt x)
  (cbrt-iter 1.0 x x))

(cbrt 8)

; Exercise 1.9
; (define (+ a b)
;   (if (= a 0)
;       b
;       (inc (+ (dec a) b))))
; (+ 4 5)
; (inc (+ 3 5))
; (inc (inc (+ 2 5)))
; (inc (inc (inc (+ 1 5))))
; (inc (inc (inc (inc (+ 0 5)))))
; (inc (inc (inc (inc 5))))
; (inc (inc (inc 6)))
; (inc (inc 7))
; (inc 8)
; 9

; (define (+ a b)
;   (if (= a 0)
;       b
;       (+ (dec a) (inc b))))
; (+ 4 5)
; (+ 3 6)
; (+ 2 7)
; (+ 1 8)
; (+ 0 9)
; 9


; Exercise 1.10
(define (A x y)
  (cond ((= y 0) 0)
        ((= x 0) (* 2 y))
        ((= y 1) 2)
        (else (A (- x 1)
                 (A x (- y 1))))))
(A 1 10)
(A 2 4)
(A 3 3)

; Exercise 1.11
; (define (f n)
;   (if (< n 3)
;       n
;       (+ (f (- n 1))
;          (* 2 (f (- n 2)))
;          (* 3 (f (- n 3))))))

(define (f n)
  (define (iter a b c n)
    (if (= n 2)
        a
        (iter (+ a (* 2 b) (* 3 c)) a b (- n 1))))
  (iter 2 1 0 n))
(f 5)

; Exercise 1.12
(define (tail lst)
  (cond ((null? lst) '())
        ((single? lst) (car lst))
        (else (tail (cdr lst)))))

(define (Pascal-triangle n)
  (define (gen lst)
    (define (last-line lst)
      (cond ((single? lst) '())
            (else (cons (+ (first lst)
                           (second lst))
                        (last-line (cdr lst))))))
    (append lst
            (list (cons 1
                        (append (last-line (tail lst))
                                '(1))))))
  (cond ((= n 1) '(1))
        ((= n 2) '((1) (1 1)))
        (else (gen (Pascal-triangle (- n 1))))))
(Pascal-triangle 6)

这题我自己都不知道是怎么过的,感觉Scheme的序列很诡异,序列(list)和有序对(pair)到底谁是妈啊?!R6RS(Scheme标准)里倒是给出了序列的递归定义:序列要么是个空序列;要么是个有序对,其后一部分须为序列(A list can be defined recursively as either the empty list or a pair whose cdr is a list.)。照这个定义,严格地说,任何序列的最后一个元素都必须是空序列(空序列不是有序对),因为结尾的必须是序列嘛,没说可以是元素哦。但事实上,也不会抠这死理,这种不以空序列结尾的有序对链叫做improper list(它不是list

)。注意,(a b c . d) 和(a . (b . (c . d)))是等价的。

只有弄清了序列和有序对的区别,你才能搞清楚为什么(cdr '(1 2)) 返回(2),而(cdr '(1 . 2))返回2,以及为什么(cons 8 9) 产生的是一个有序对(8 . 9),而(cons 8 '(9))生成的却是序列(8 9)。

练习1.13

要求证Fib(n)是最接近Φn/√5的整数,即 | Fib(n)-Φn/√5 | < 1。而Fib(n) = (Φnn) /√5, Φ = (1 + √5)/2, γ = (1 - √5)/2, 所以 Φn/√5 - Fib(n) = γn/√5 = (-1)n((√5-1)/2)n/√5。众所周知, (√5-1)/2 是黄金分割点0.618,其除以 √5肯定比1小,所以 Φn/√5 - Fib(n) 的绝对值小于1,故得证。

现在的问题是,你怎么知道Fib(n) = (Φnn) /√5 ?

这是一个来自中学奥赛的古老技巧,不知道它是怎么来的,但它很优美地就把问题解决了,就好像从没存在过这个问题一样。

令fn = Fib(n),设fn - αfn-1 = β(fn-1 - αfn-2 ) ,则α + β = 1且- αβ = 1,即α = (1 + √5)/2, β = (1 - √5)/2(或者对调)

所以fn = αn-1 + αn-2β + ... + αβn-2 + βn-1(此处省去若干字)。

然后呢,我想了很久,曾试图拆成两半,利用 αβ = -1的特性来化为等比数列⋯⋯最后放弃了,因为结果很复杂。它不应长这个样子。

然后,我又看了看提示,突然意识到,曾几何时有这么个恒等式:

αn - βn = (α - β)(αn-1 + αn-2β + ... + αβn-2 + βn-1)

-_-!!

练习1.14

所需空间为调用树的高度,即n / min{coins[k]};

其所需空间的阶为Θ(n)。

设C(n, k)为以k种硬币兑换n元的方案数,coins为一数组,其元素的值为各种硬币面值,则

C(n, k) = C(n, k-1) + C(n-coins[k], k)

然后,⋯⋯,好吧,我承认,我不知道该怎么解,甚至连大致的阶也猜不出来-_-!!

; Exercise 1.15
(define (cube x) (* x x x))
(define (p x) (- (* 3 x) (* 4 (cube x))))
(define (sine angle)
   (if (not (> (abs angle) 0.1))
       angle
       (p (sine (/ angle 3.0)))))
(sine 12.15)
(p (sine 4.05))
(p (p (sine 1.35)))
(p (p (p (sine 0.45))))
(p (p (p (p (sine 0.15)))))
(p (p (p (p (p (sine 0.05))))))
⋯⋯

  1. 可见p将被调用5次。
  2. 在求(sine a)的值时,需要至少n层栈,n满足:

a / 3n < 0.1

即,n > log3 10a

故需要的空间的阶为Θ(log n)。

因其计算过程分为递归展开至终止情况(即弧度不大于0.1)和回退两部分,故步数为所需空间的常数倍,即Θ(log n)。

为了得到调用sine的实际步数,添加一个参数c(作为累计量),返回结果变为一个有序对(正弦值, 调用次数):

(define (inc x) (+ x 1))
(define (cube x) (* x x x))
(define (p x) (- (* 3 x) (* 4 (cube x))))
(define (sine angle c)
   (if (not (> (abs angle) 0.1))
       (cons angle (inc c))
       (let ((res (sine (/ angle 3.0) (inc c))))
        (cons (p (car res)) (cdr res)))))
(sine 12.15 0) ; => (-0.39980345741334 . 6)

; Exercise 1.16
(define (fast-expt b n)
  (define (expt-iter b counter product)
    (cond ((= counter 0) 1)
          ((= counter 1) (* b product))
          ((even? n) (expt-iter (square b) (/ counter 2) product))
          (else (expt-iter b (- counter 1) (* b product)))))
  (expt-iter b n 1))

以b7为例

b7 = b6 ·b

    = (b2)3 ·b

    = (b2)2 ·(b2 · b)

    = (b4)1 ·(b2 · b)

    = b4 ·(b2 · b)

斜体部分就是product,用来存储幂为奇数时产生的尾数。

; Exercise 1.17


(define (double n)
  (+ n n))
(define (halve n)
  (/ n 2))
(define (fast-prod a b)
  (cond ((= a 0) 0)
        ((even? a) (double (fast-prod (halve a) b)))
        (else (+ (fast-prod (- a 1) b) b))))
(fast-prod 4 3)

; Exercise 1.18
(define (double n)
  (+ n n))
(define (halve n)
  (/ n 2))
(define (fast-prod a b sum)
  (cond ((= a 0) sum)
        ((even? a) (fast-prod (halve a) (double b) sum))
        (else (fast-prod (- a 1) b (+ b sum)))))
(define (prod a b)
  (fast-prod a b 0))

(prod 5 4)

俄罗斯农民算法是这样进行的:第一乘数不断除2下取整直至为1,第二乘数不断乘2,如此罗列下来。然后把所有第一乘数为偶数的行划去,接着把第二乘数那一列加起来,即为乘积。

5    4

2    8

1  16

-------

    20


换个写法:

5 × 4

= 4 × 4 + 4

= 2 × 8 + 4

= 1 × 16 + 4

= 0 × 16 + (16 + 4)

sum就是用来存储第一乘数为奇数时的累加值。

; Exercise 1.19
(define (square n)
  (* n n))
(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
                   (+ (square p) (square q))     ; compute p'
                   (+ (square q) (* 2 p q))      ; compute q'
                   (/ count 2)))
        (else (fib-iter (+ (* b q) (* a q) (* a p))
                        (+ (* b p) (* a q))
                        p
                        q
                        (- count 1)))))

(fib 7)

变换规则

a’ ← b q + a q + a p 

b’ ← b p + a q

应用两次后,得:

a’ ← b Q + a Q + a P 

b’ ← b P + a Q

其中,P = p2 + q2,Q = q2 + 2 p q

谁想出来的,太GG神奇了!

(待续……)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值