《SICP》习题第1章

本人做的SICP习题第1章,如有错误请指正,用的解释器是Racket

练习1.1

计算代码如下

;; Exercise 1.1
#lang racket
10

(+ 5 3 4)

(- 9 1)

(/ 6 2)

(+ (* 2 4) (- 4 6))

(define a 3)

(define b (+ a 1))

(+ a b (* a b))

(= a b)

(if (and (> b a) (< b (* a b)))
    b
    a)

(cond ((= a 4) 6)
      ((= b 4) (+ 6 7 a))
      (else 25))

(+ 2 (if (> b a) b a))

(* (cond ((> a b) a)
         ((< a b) b)
         (else -1))
   (+ a 1))

10

12

8

3

6

a

b

19

#f

4

16

6

16

练习1.2

;; Exercise 1.2
#lang racket

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

答案为-37/150

练习1.3

;; Exercise 1.3
;; 返回三个数中较大两个数的平方和
#lang racket

;; 返回两个输入的较小数
(define (min a b)
  (if (< a b)
      a
      b))

;; 返回三个数中最小的一个
(define (min-three a b c)
  (min (min a b)
       (min b c)))

;; 求平方
(define (square x)
  (* x x))

;; 返回三个数中较大两个数的平方和
;; 先计算三个数的平方和,再减去最小数的平方
(define (two-larger-square-sum a b c)
  (- (+ (square a) (square b) (square c))
     (square (min-three a b c))))

练习1.4

讲道理嘛,这个看函数名儿都能看出来

不过这里可以看出,操作符也可以作为一个combination的返回值

;; Exercise 1.4
#lang racket

(define (a-plus-abs-b a b)
  ((if (> b 0) + -) a b))

a + abs(b)

练习1.5

这题我卡了非常久,第一次做的时候没有仔细想就随便过去了,做到后面1.20的时候觉得不对,又回来看1.5和1.6两题,然后画了一天理解什么是应用序和正则序,具体的思路整理在另一篇博客里刷SICP遇到的问题——深入学习理解正则序和应用序

看一下题目里的这段代码

;; Exercise 1.5
#lang racket

;; 定义一个死循环递归
(define (p) (p))

;; 检测解释器是应用序还是正则序
(define (test x y)
  (if (= x 0)
      0
      y))

(test 0 (p))

(define (p) (p))定义了一个死循环函数,这是一个最简单的递归,所以一旦求解(p)这个表达式,就是进入这个递归死循环

如果是应用序,在执行(test 0 (p))的时候就会去求解p,我试了MIT-Scheme和Racket两个解释器,都会卡住,显然都是应用序

如果是正则序,会正常返回0,在执行(test 0 (p))后就替换为(if (= 0 0) 0 (p)),条件表达式满足,(p)就被忽略掉了,所以会正常返回0,我试了lazyracket这个惰性求值的解释器(实在找不到正则序的),可以返回0。切换到lazyracket很简单,开头那里定义成#lang lazy就行

练习1.6

这段代码

;; Exercise 1.6
#lang racket

;; 定义新if
(define (new-if predicate then-clause else-clause)
  (cond (predicate then-clause)
        (else else-clause)))

;; 求平方
(define (square x)
  (* x x))

;; 检测猜测值精度
(define (good-enough? guess x)
  (< (abs (- (square guess) x)) 0.001))

;; 牛顿法改进猜测值
(define (improve guess x)
  (average guess (/ x guess)))

;; 求平均值
(define (average x y)
  (/ (+ x y) 2))

;; 不断改进猜测值直到精度满足需求
(define (sqrt-iter guess x)
  (begin
    (new-if (good-enough? guess x)
        guess
        (sqrt-iter (improve guess x) x))))

;; 牛顿法求平方根
(define (sqrt x)
  (sqrt-iter 1.0 x))

对于应用序,必然会卡死啊,在执行一个new-if的时候求解每个参数的值,这样就陷入了sqrt-iter的无限递归

对于正则序,展开一个看看,(sqrt-iter 1.0 9)展开为

(cond ((good-enough? 1.0 9) guess)
          (else (sqrt-iter (improve guess x)
                                  x))))

后面的不展开了,如果cond也是特殊处理过的,所以在某次good-enough?返回为真的情况下,不再继续往下展开sqrt-iter,程序可以正确执行,所以我猜测在正则序下这个代码是可以运行的

验证一下,用lazyracket

妥妥滴

练习1.7

对于很小的数,精度不足,这个非常好理解

对于很大的数,会卡死,因为浮点数的位数是有限的,无法达到0.001这么小的精度差,比如2130895720398745908273049857902374590723904570237409572394750937252394892817059712093847098129034709823904812093875091620934213089572039874590827304985790237459072390457023740957239475093725这个数就会在原来的sqrt程序那里卡死

我知道怎么退出了,在运行窗口连按两个C-c

改进good-enough?,监视猜测值guess,如果improve出的新guess和原guess差小于某个值就停止迭代,代码如下

;; Exercise 1.7
;; 改进的牛顿迭代法
#lang racket

;; 平方
(define (square x)
  (* x x))

;; 检测猜测值精度,当猜测值和上一次猜测值相比改进小于0.1%时停止迭代
(define (good-enough? old-guess guess)
  (< (abs (- old-guess guess)) (* guess 0.001)))

;; 改进猜测值
(define (improve guess x)
  (average guess (/ x guess)))

;; 平均值
(define (average x y)
  (/ (+ x y) 2))

;; 牛顿迭代法
(define (sqrt-iter old-guess guess x)
  (begin
    (if (good-enough? old-guess guess)
        guess
        (sqrt-iter guess (improve guess x) x))))

;; 改进的求平方根
(define (better-sqrt x)
  (sqrt-iter 0.0 1.0 x))

练习1.8

代码如下

;; Exercise 1.8
#lang racket

;; 立方
(define (cube x)
  (* x x x))

;; 猜测值精度检测
(define (good-enough? guess x)
  (< (abs (- (cube guess) x)) 0.001))

;; 迭代改进猜测值
(define (improve guess x)
  (/ (+ (/ x (* guess guess)) (* 2 guess)) 3))

;; 牛顿迭代法
(define (cube-root-iter guess x)
  (begin
    (if (good-enough? guess x)
        guess
        (cube-root-iter (improve guess x) x))))

;; 求立方根
(define (cube-root x)
  (cube-root-iter 1.0 x))

(cube-root 729)

练习1.9

第一个程序

;; Exercise1.9
;; 递归加法
#lang racket

;; 减一
(define (dec a)
  (- a 1))

;; 加一
(define (inc a)
  (+ a 1))

;; 递归加法
(define (add a b)
  (if (= a 0)
      b
      (inc (add (dec a) b))))

展开:

(+ 4 5)

(inc (+ 3 5))

(inc (inc (+ 2 5)))

(inc (inc (inc (1 5))))

(inc (inc (inc (inc 5))))

递归

第二个程序

;; Exercise1.9
;; 循环加法
#lang racket

;; 减一
(define (dec a)
  (- a 1))

;; 加一
(define (inc a)
  (+ a 1))

;; 循环加法
(define (add a b)
  (if (= a 0)
      b
      (add (dec a) (inc b))))

展开:

(+ 4 5)

(+ 3 6)

(+ 2 7)

(+ 1 8)

(+ 0 9)

循环

练习1.10

代码如下,用于验证计算结果

;; Exercise 1.10
;; 阿克曼函数
#lang racket

;; 阿克曼函数
(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 1 10) = (A 0 (A 1 9)) = (* 2 (A 1 9))

(A 1 9) = (A 0 (A 1 8)) = (* 2 (A 1 8))

以此类推 (A 1 10) = (* 512 (A 1 1)) = 1024

因此可以看出来(A 1 n)就是2^n

(A 2 4):

(A 2 4) = (A 1 (A 2 3))

(A 2 3) = (A 1 (A 2 2))

(A 2 2) = (A 1 (A 2 1))

所以(A 2 4) = 2^(2^(2^2))) = 65536

可以看出来(A 2 n)就是n个2这样 2^(2^(2^……)))

(A 3 3):

(A 3 3) = (A 2 (A 3 2)))

(A 3 2) = (A 2 (A 3 1)))  = (A 2 2) = 4

所以(A 3 3) = (A 2 4) = 65536

(define (f n) (A 0 n)):

(A 0 n) = (* 2 n) = 2n

(define (g n) (A 1 n)):

(A 1 n) = (A 0 (A 1 (n -1))) = (* 2 (A 1 (n - 1))) = …… = 2^(n-1) * (A 1 1) = 2^n

(define (h n) (A 2 n)):

(A 2 n) = (A 1 (A 2 (n - 1))) = 2^(A 2 (n - 1)) = …… = 2^(2^(2^……(A 2 1)))  = 2^(2^(2^……)))(共n个2)

(define (k n) (* 5 n n)):

5n^2啦

练习1.11

递归的代码

;; Exercise 1.11
;; 递归求f函数值
#lang racket

;; 递归求f
(define (f n)
  (cond ((< n 3) n)
        (else (+ (f (- n 1)) (* 2 (f (- n 2))) (* 3 (f (- n 3)))))))

迭代的代码

;; Exercise 1.11
;; 迭代求f函数值
#lang racket

;; 迭代求f
(define (f-iterative f1 f2 f3 n)
  (cond ((< n 3) n)
        ((= n 3) f1)
        (else (f-iterative (+ f1 (* 2 f2) (* 3 f3)) f1 f2 (- n 1)))))

;; 求f
(define (f n)
  (f-iterative 4 2 1 n))

练习1.12

row和col从1开始,严格来说这个函数是不严谨的,没有考虑输入不合法的情况

;; Exercise 1.12
;; 求pascal三角中的数值
#lang racket

;; 求第row行第col列的pascal三角数值
(define (pascal row col)
  (cond ((or (= col 1) (= col row)) 1)
        (else (+ (pascal (- row 1) col) (pascal (- row 1) (- col 1))))))

练习1.13

先用数学归纳法证明Fib(n) = \frac{\Phi ^{n} - \Psi ^{n}}{\sqrt{5}},在我遥远模糊的记忆中数学归纳法的格式差不多应该是这样,如果不符合规范,反正是我初中老师就这么教的

1° 当n = 0,代入数值计算可得Fib(0) = \frac{\Phi ^{0} - \Psi ^{0}}{\sqrt{5}}

    当n = 1,代入数值计算可得Fib(1) = \frac{\Phi ^{1} - \Psi ^{1}}{\sqrt{5}}

2° 假设存在

          Fib(n-1) = \frac{\Phi ^{n-1} - \Psi ^{n-1}}{\sqrt{5}}以及Fib(n-2) = \frac{\Phi ^{n-2} - \Psi ^{n-2}}{\sqrt{5}}

    则有

          Fib(n) = Fib(n-1) + Fib(n-2) = \frac{\Phi ^{n-1} - \Psi ^{n-1}}{\sqrt{5}} + \frac{\Phi ^{n-2} - \Psi ^{n-2}}{\sqrt{5}}

                       = \frac{\Phi ^{n-2}(\Phi+1) - \Psi ^{n-2}(\Psi+1)}{\sqrt{5}}

                       = \frac{\Phi ^{n}- \Psi ^{n}}{\sqrt{5}}

3° 由1°、2°,可得Fib(n) = \frac{\Phi ^{n} - \Psi ^{n}}{\sqrt{5}}

公式真难写,赶快保存一下

现在证明\frac{\Phi ^{n}}{\sqrt{5}}最接近的整数是Fib(n)

计算可得\Psi \approx -0.618,因此\left | \frac{\Psi^{n}}{\sqrt{5}} \right | < 0.5

由二项式的展开式可得,\frac{\Phi ^{n}}{\sqrt{5}}\frac{\Psi ^{n}}{\sqrt{5}}必然不是整数

一个非整数减去一个绝对值小于0.5的非整数,所得到的整数必然是最接近这个非整数的整数嘛(取整有两个方向,向上取整或向下取整,现在朝一个取整方向的距离小于0.5,那到另一个取整方向的距离必然大于0.5了)

练习1.14

我非常无聊地真的画了出来,第一次画完发现,我把硬币的大小弄反了,题目代码是从大到小,我变成从小到大,导致多了非常多步骤,不管,错误的图我也要贴出来

底下这张是正确的,我并没有检查,应该也没有无聊的人会去检查,一共标红的4种情况是可以的

space是\Theta (n),step是\Theta (\alpha ^{n}}), 1<\alpha <2

练习1.15

a. \log_3{(12.15 / 0.1)} \approx 4.37,所以调用了4次p函数

b. space和step都是\Theta (\log{n})

练习1.16

;; Exercise 1.16
#lang racket

;; 迭代求幂
(define (exp-iter a b n)
  (if (= n 0) a
      (if (even n) (exp-iter a (square b) (/ n 2))
          (exp-iter (* a b) b (- n 1)))))

;; 快速求幂
(define (fast-exp b n)
  (exp-iter 1 b n))

;; 判断是否为偶数
(define (even n)
  (= (remainder n 2) 0))

;; 平方
(define (square x)
  (* x x))

练习1.17

;; Exercise 1.17
;; 快速乘法
#lang racket

;; 倍增
(define (double x)
  (+ x x))

;; 减半
(define (halve x)
  (/ x 2))

;; 检测是否为偶数
(define (even x)
  (= (remainder x 2) 0))

;; 快速乘法
(define (fast-mult a b)
  (cond ((= b 0) 0)
        ;; 处理b为负的情况
        ((< b 0) (- 0 (fast-mult a (- 0 b))))
        ((even b) (double (fast-mult a (halve b))))
        (else (+ a (fast-mult a (- b 1))))))

练习1.18

递归改循环很简单,不要对递归调用的返回值做额外计算就是迭代了

;; Exercise 1.18
;; 迭代快速乘法
#lang racket

;; 倍增
(define (double x)
  (+ x x))

;; 减半
(define (halve x)
  (/ x 2))

;; 检测是否为偶数
(define (even x)
  (= (remainder x 2) 0))

;; 快速乘法
(define (fast-mult-iterative a b)
  (cond ((= b 0) 0)
        ;; 处理b为负的情况
        ((< b 0) (- 0 (fast-mult-iterative a (- 0 b))))
        ;; 改递归为迭代
        ((even b) (fast-mult-iterative (double a) (halve b)))
        (else (+ a (fast-mult-iterative a (- b 1))))))

练习1.19

首先证明,变换T_{pq}: a\leftarrow bq+aq+ap, b\leftarrow bp+aq

两次变换相当于一次变换T_{p'q'}

a_1\leftarrow b_0q+a_0q+a_0p, b_1\leftarrow b_0p+a_0q

a_2\leftarrow (b_0p+a_0q)q+(b_0q+a_0q+a_0p)(q+p)

a_2\leftarrow (2pq+q^2)b_0+(2q^2+p^2+2pq)a_0

b_2\leftarrow (b_0p+a_0q)p+(b_0q+a_0q+a_0p)q

b_2\leftarrow (p^2+q^2)b_0+(2pq+q^2)a_0

则一次变换T_{p'q'}相当于两次变换T_{pq},其中

p' = p^2+q^2, q' = 2pq+q^2

对于裴波那契数,则有

Fib(n-1) = Fib(n-2) + Fib(n-3), Fib(n) = Fib(n-1) + Fib(n-2)

Fib(n) = Fib(n-1) + Fib(n-2), Fib(n+1) = Fib(n) + Fib(n-1)

a_{n-1} \rightarrow Fib(n-1), b_{n-1} \rightarrow Fib(n-2)

 a_{n} = a_{n-1} + b_{n-1}, b_{n} = a_{n-1}

相当于变换T_{01},两次变换T_{01}可以用一次变换T_{11}代替,即

a_{n} = 2a_{n-2} + b_{n-2}, b_{n} = a_{n-1}

代码如下

;; Exercise 1.19
;; 快速求解裴波那契数
#lang racket

;; 求解裴波那契数
(define (fast-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))
                   (+ (* 2 p q) (square q))
                   (/ count 2)))
        (else (fib-iter (+ (* b q) (* a q) (* a p))
                        (+ (* b p) (* a q))
                        p
                        q
                        (- count 1)))))

;; 平方
(define (square x)
  (* x x))

;; 判断是否为偶数
(define (even? x)
  (= (remainder x 2) 0))

练习1.20

正则序会比应用序多调用remainder灰常多次,因为a、b在正则序里都会展开为remainder求值

用下面这段程序在racket和MIT-Scheme两个解释器上测试

;; Exercise 1.20
;; 计算欧几里得算法调用remainder函数次数
#lang racket

;; 重新定义remainder函数,每次调用都打出一个yes
(define (remainder-count a b)
  (display "yes\n")
  (remainder a b))

;; 欧几里得算法
(define (gcd a b)
  (if (= b 0)
      a
      (gcd b (remainder-count a b))))

(gcd 206 40)

racket是应用序,一共输出4个yes,就是调用4次remainder

正则序展开,我只展开了一点,完全展开的话,画面太美……

(gcd 206 40)

(if (= b 0) a ((if (= (remainder a b) 0) b (gcd (remainder a b) (remainder b (remainder a b))))))))

……

总之,非常多remainder调用

这个我用lazyracket也试了一下,照样返回4个yes,所以惰性求值和正则序还是有区别的,不会简单粗暴地一撸到底

现在来计算次数,我是不会展开的,用了另一种方法来做

调用的过程是(gcd 206 40) → (gcd 40 6) → (gcd 6 4) → (gcd 4 2) ,逆推回去

(gcd 2 0)执行了:

(if (= 0 0)

    2))

这里调用1个0,1个2,0通过(remainder 4 2)得到,2通过(remainder 6 4)得到,4通过(remainder 40 6)得到,6通过(remainder 206 40)得到

所以(gcd 2 0)里面,0通过6次remainder得到,2通过4次remainder,4通过2次remainder,6通过1次remainder,一共调用了13次

对于其他的gcd过程,尾递归的(gcd b (remainder a b))中a和b只是传递到了下一层,最终传递到(gcd 2 0)才开始规约,所以不需要考虑,if条件的a分支在条件判断后就被跳过,真正调用了remainder的只有条件判断里的b

对于(gcd 4 2),判断2是否为0,通过4次remainder得到

对于(gcd 6 4),判断4是否为0,通过1次remainder得到

对于(gcd 206 40),判断40是否为0,没有调用remainder

所以加起来是13 + 4 + 1 = 18

网上有人做了完全展开,结果也是18,见这篇博文SICP_exercise_1.20

练习1.21

求解代码

;; 求输入的最小因子
#lang racket
(provide (all-defined-out))
 
;; 平方
(define (square x)
  (* x x))

;; 求最小因子
(define (smallest-divisor n)
  (find-divisor n 2))

;; 迭代
(define (find-divisor n test-divisor)
  (cond ((> (square test-divisor) n) n)
        ((divides? test-divisor n) test-divisor)
        (else (find-divisor n (+ test-divisor 1)))))

;; 判断是否可以除尽
(define (divides? a b)
  (= (remainder b a) 0))

(smallest-divisor 199)
(smallest-divisor 1999)
(smallest-divisor 19999)

199和1999都是质数,19999最小因子是7

练习1.22

我用的是racket,没有runtime,用了current-inexact-milliseconds代替

引用了练习1.21的最小因数函数

先写一个判断是否是质数的代码

;; 通过求解最小因数,判断是否是质数
#lang racket
(provide (all-defined-out))
(require "smallest-divisor.rkt")

;; 最小因数等于本身,证明为质数
(define (prime-test-by-smallest-divisor x)
  (and (> x 1) (= (smallest-divisor x) x)))

然后写一个搜索质数,现在计算机太快,题目里那么小的数一会就算完了,所以加大了很多倍

;; Exercise 1.22
;; 搜索给定范围的质数,并打印时间
;; 判断质数使用最小因子法
#lang racket
(require "prime-test-by-smallest-divisor.rkt")

;; 平方
(define (square x)
  (* x x))

;; 搜索指定数量和范围的质数并打印
(define (start-prime-test n start-time number)
  (cond ((= number 0) (report-prime (- (current-inexact-milliseconds) start-time)))
        ((prime-test-by-smallest-divisor n)
         (display n)
         (display " ")
         (start-prime-test (+ n 1) start-time (- number 1)))
        (else (start-prime-test (+ n 1) start-time number))))

;; 打印程序耗费时间
(define (report-prime elapsed-time)
  (display "*** ")
  (display elapsed-time)
  (newline))

;; 开始质数搜索,并记录当前时间
(define (searchForPrimes startNumber number)
  (start-prime-test startNumber (current-inexact-milliseconds) number))      
;; 求幂的模
(define (expmod base n m)
  (cond ((= n 0) 1)
        ((odd? n) (remainder (* base (expmod base (- n 1) m)) m))  
        (else (remainder (square (expmod base (/ n 2) m)) m))))

(searchForPrimes 10000000000 3)
(searchForPrimes 100000000000 3)
(searchForPrimes 1000000000000 3)
(searchForPrimes 10000000000000 3)
(searchForPrimes 100000000000000 3)
n 时间 比例
10000000000 9.894  
100000000000 28.444 2.874874
1000000000000 74.334 2.613346
10000000000000 302.888 4.07469
100000000000000 989.38 3.266488

好像并不是十分地严格啊,当n增加比例会相对于近似理论值

练习1.23

改进的最小因子求解代码如下

;; Exercise 1.23
;; 改进的求最小因子函数
;; 当不能被2整除,跳过所有偶数因子的尝试
#lang racket
(provide (all-defined-out))

;; 迭代搜索最小因子
(define (fast-divisor-iter n divisor)
  (cond ((> (* divisor divisor) n) n)
        ((divides? n divisor) divisor)
        (else (fast-divisor-iter n (next divisor)))))

;; 改进的求解最小因子函数
(define (fast-smallest-divisor n)
  (fast-divisor-iter n 2))

;; 判断是否可以除尽
(define (divides? a b)
  (= (remainder a b) 0))

(define (next divisor)
  (if (= divisor 2)
      3
      (+ divisor 2)))

改进后的质数测试

;; Exercise 1.23
;; 改进版通过求解最小因数,判断是否是质数
#lang racket
(provide (all-defined-out))
(require "fast-smallest-divisor.rkt")

;; 最小因数等于本身,证明为质数
(define (fast-prime-test-by-smallest-divisor x)
  (= (fast-smallest-divisor x) x))

改进后的质数搜索

;; Exercise 1.22
;; 搜索给定范围的质数,并打印时间
;; 判断质数使用最小因子法
#lang racket
(require "fast-prime-test-by-smallest-divisor.rkt")

;; 平方
(define (square x)
  (* x x))

;; 搜索指定数量和范围的质数并打印
(define (start-prime-test n start-time number)
  (cond ((= number 0) (report-prime (- (current-inexact-milliseconds) start-time)))
        ((fast-prime-test-by-smallest-divisor n)
         (display n)
         (display " ")
         (start-prime-test (+ n 1) start-time (- number 1)))
        (else (start-prime-test (+ n 1) start-time number))))

;; 打印程序耗费时间
(define (report-prime elapsed-time)
  (display "*** ")
  (display elapsed-time)
  (newline))

;; 开始质数搜索,并记录当前时间
(define (searchForPrimes startNumber number)
  (start-prime-test startNumber (current-inexact-milliseconds) number))      
;; 求幂的模
(define (expmod base n m)
  (cond ((= n 0) 1)
        ((odd? n) (remainder (* base (expmod base (- n 1) m)) m))  
        (else (remainder (square (expmod base (/ n 2) m)) m))))

(searchForPrimes 10000000000 3)
(searchForPrimes 100000000000 3)
(searchForPrimes 1000000000000 3)
(searchForPrimes 10000000000000 3)
(searchForPrimes 100000000000000 3)

替换之后,时间如下

n 时间 时间/改进前时间
10000000000 4.064 41.08%
100000000000 16.261 57.17%
1000000000000 43.798 58.92%
10000000000000 186.143 61.46%
100000000000000 603.598 61.01%

时间大约缩短了一小半,貌似越大的数缩小的越少

练习1.24

先写好费马小定理对应的质数检验函数

;; Exercise 1.24
;; 通过费马小定理检验是否为质数
#lang racket
(provide (all-defined-out))

;; 求幂的取模
(define (expmod base n m)
  (cond ((= n 0) 1)
        ((odd? n) (remainder (* base (expmod base (- n 1) m)) m))  
        (else (remainder (square (expmod base (/ n 2) m)) m))))

;; 平方
(define (square x)
  (* x x))

;; 判断是否为奇数
(define (odd? n)
  (= (remainder n 2) 1))

;; 一次费马小定理的质数检验
(define (fermat-test n)
  (define (try-it a)
    (= (expmod a n n) a))
  (try-it (+ 1 (random (- n 1)))))

;; 费马小定理检验质数
(define (prime-test-by-fermat-iter n times)
  (cond ((= times 0) true)
        ;; 若一次检验通过,次数减一,再次检验
        ((fermat-test n) (prime-test-by-fermat-iter n (- times 1)))
        (else false)))

;; 费马小定理检验是否为质数,n表示检验次数
(define (prime-test-by-fermat n)
  (prime-test-by-fermat-iter n 10))

然后编写改进后的质数搜索函数,因为时间实在太小,把数增加到很大,寻找质数的个数也设为了3000

;; Exercise 1.24
;; 通过费马小定理检验质数法,搜索给定范围内的质数
#lang racket
(require "prime-test-by-fermat.rkt")

;; 平方
(define (square x)
  (* x x))

;; 搜索指定数量和范围的质数并打印
(define (start-prime-test n start-time number)
  (cond ((= number 0) (report-prime (- (current-inexact-milliseconds) start-time)))
        ((prime-test-by-fermat n)
         (start-prime-test (+ n 1) start-time (- number 1)))
        (else (start-prime-test (+ n 1) start-time number))))

;; 打印程序耗费时间
(define (report-prime elapsed-time)
  (display "*** ")
  (display elapsed-time)
  (newline))

;; 开始质数搜索,并记录当前时间
(define (searchForPrimes startNumber number)
  (start-prime-test startNumber (current-inexact-milliseconds) number))

(searchForPrimes 1000000 3000)
(searchForPrimes 100000000 3000)

运行了三次,结果如下,理论上呢大输入的运行时间应该是小输入的两倍,实际是比两倍少一些

这个原因大家应该都很清楚,程序的用时不是严格等于执行的步数,还有很多其他因素,这里不多说

练习1.25

这段代码理论上是可以执行的,实践中,对于较小的数下面程序可以运行

但是输入增加后,幂增加到很大,最后的计算结果是一个灰常大的整数,scheme处理灰常大的整数会很卡

而之前的函数可以很快返回结果

因为之前的函数每迭代一次求幂,就取模一次,所以幂(的余数)一直限制在一个很小的范围

练习1.26

因为expmod在每次迭代过程中都计算了两次

讨论个理想情况,假设exp = N = 2^n,在计算expmod的时候的调用如下:

(expmod, 2^n)调用了2次(expmod, 2^{n-1})

(expmod, 2^{n-1})调用了2次(expmod, 2^{n-2})

……

(expmod, 2^{1})调用了2次(expmod, 2^{0})

总调用次数为:1+2^1+2^2+......+2^{n} = 2^{n+1} - 1 = 2N - 1

对于不完全等于2次幂的N来说,也有类似的结果,证明这个程序是\Theta (n)

练习1.27

做了十次费马小定理检验,这6个数字都通过了

这种数叫啥正合成数,也叫伪素数,他们是可以通过费马小定理的检验的

练习1.28

修改一下square函数,在平方后做一下检测

;; Exercise 1.28
;; 通过Miller-Rabin测试检验是否为质数
#lang racket

;; 求幂的取模,若检测到某次迭代幂模为1,返回0,其余情况返回幂的取模结果
(define (expmod base n m)
  (cond ((= n 0) 1)
        ((odd? n) (remainder (* base (expmod base (- n 1) m)) m))  
        (else (remainder (square-and-check (expmod base (/ n 2) m) m) m))))

;; 平方,检测平方结果对m的模,为1则返回0,其余情况返回平方结果
(define (square-and-check x m)
  (define res (* x x))
  (cond ((or (= x 1) (= x (- m 1))) res)
        ((= (remainder res m) 1) 0)
        (else res)))

;; 判断是否为奇数
(define (odd? n)
  (= (remainder n 2) 1))

;; 一次质数检验
(define (fermat-test n)
  (define (try-it a)
    (= (expmod a n n) a))
  (try-it (+ 1 (random (- n 1)))))

;; 质数检验
(define (prime-test-by-miller-rabin-iter n times)
  (cond ((= times 0) true)
        ;; 若一次检验通过,次数减一,再次检验
        ((fermat-test n) (prime-test-by-miller-rabin-iter n (- times 1)))
        (else false)))

;; 检验是否为质数,n表示检验次数
(define (prime-test-by-miller-rabin n)
  (prime-test-by-miller-rabin-iter n 10))

练习1.29

不要以a作为自变量,转变思路,用k做自变量写起来更加方便

;; Exercise 1.29
;; Simpson's Rule计算integral
#lang racket
(require "cube.rkt")
(require "sum.rkt")

;; Simpson's Rule计算integral
(define (integral-by-simpson f a b n)
  ;; 定义h
  (define h (/ (- b a) n))
  ;; 定义h/3
  (define h-divide-3 (/ h 3))
  ;; 以k作为自变量,比用a方便
  (define (next k)
    (+ k 1))
  ;; k的每项计算
  (define (term k)
    (define yk (* h-divide-3 (f (+ a (* k h))))) 
    (cond ((or (= k 0) (= k n)) yk)
          ((even? k) (* 2 yk))
          (else (* 4 yk))))
  ;; 调用sigma函数
  (sum term 0 next n))

;; 判断是否为偶数
(define (even? x)
  (= (remainder x 2) 0))

(integral-by-simpson cube 0 1 100)
(integral-by-simpson cube 0 1 1000)

cube就是求立方的代码很简单,如下

;; 立方计算
#lang racket
(provide (all-defined-out))

(define (cube x)
  (* x x x))

sum是求sigma的

;; sigma计算
#lang racket
(provide (all-defined-out))

(define (sum term a next b)
  (if (> a b)
      0
      (+ (term a)
         (sum term (next a) next b))))

n是100或者1000出来的结果都是1/4

练习1.30

写出迭代的程序,技巧就是递归调用的时候,外面不要再套其他操作

;; Exercise 1.30
;; 迭代版的sigma
#lang racket

(define (sum term a next b)
  (define (sum-iter a res)
    (if (> a b)
        res
        (sum-iter (next a) (+ res (term a)))))
  (sum-iter a 0))

练习1.31

a.

递归的,答案是3.141592810665997

;; Exercise 1.31
;; 递归的product
#lang racket

;; 递归的product
(define (product term a next b)
  (if (> a b)
      1
      (* (product term (next a) next b)
         (term a))))

;; 判断是否为偶数
(define (even? x)
  (= (remainder x 2) 0))

;; 计算pi
(define (pi-product n)
  (define (next k)
    (+ k 1))
  (define (term k)
    (/ (if (even? k)
           (+ k 2.0)
           (+ k 1.0))
       (if (even? k)
           (+ k 1.0)
           (+ k 2.0))))
  (* (product term 1 next n)
     4))

(pi-product 10000000)

b.

迭代的,答案略有不同,是3.1415928106682323,因为乘的先后顺序不一样了

;; Exercise 1.31
;; 迭代的product
#lang racket

;; 迭代的product
(define (product term a next b)
  (define (product-iter a res)
    (if (> a b)
        res
        (product-iter (next a) (* res (term a)))))
  (product-iter a 1))

;; 判断是否为偶数
(define (even? x)
  (= (remainder x 2) 0))

;; 计算pi
(define (pi-product n)
  (define (next k)
    (+ k 1))
  (define (term k)
    (/ (if (even? k)
           (+ k 2.0)
           (+ k 1.0))
       (if (even? k)
           (+ k 1.0)
           (+ k 2.0))))
  (* (product term 1 next n)
     4))

(pi-product 10000000)

练习1.32

a.

递归版本

;; Exercise 1.32
;; 递归的accumulate
#lang racket

;; 递归accumulate
(define (accumulate combiner null-value term a next b)
  (if (> a b)
      null-value
      (combiner (accumulate combiner null-value term (next a) next b)
                (term a))))

b.

迭代版本

;; Exercise 1.32
;; 迭代的accumulate
#lang racket

;; 迭代accumulate
(define (accumulate combiner null-value term a next b)
  (define (accumulate-iter a res)
    (if (> a b)
        res
        (accumulate-iter (next a) (combiner res (term a)))))
  (accumulate-iter a null-value))

练习1.33

加条件判断的accumulate,简单

;; Exercise 1.33
;; 带filter的accumulate,迭代计算
#lang racket
(provide (all-defined-out))

;; 迭代filtered-accumulate
(define (filtered-accumulate combiner null-value term a next b filter?)
  (define (accumulate-iter a res)
    (cond ((> a b) res)
          ((filter? a) (accumulate-iter (next a) (combiner res (term a))))
          (else (accumulate-iter (next a) res))))
  (accumulate-iter a null-value))

a.

加一个素数判断,用上之前的代码

;; Exercise 1.33
;; 素数累加
#lang racket
(require (file "../Testing for Primality/prime-test-by-smallest-divisor.rkt"))
(require "filtered-accumulate.rkt")

(define (prime-accumulate a b)
  (define (next x)
    (+ x 1))
  (filtered-accumulate + 0 identity a next b prime-test-by-smallest-divisor))

(prime-accumulate 1 10)

b.

也很简单,调用之前写的欧几里得算法

;; Exercise 1.33
;; 所有与n互质且小于n的正整数累乘
#lang racket
(require "filtered-accumulate.rkt")

;; 欧几里得算法
(define (gcd a b)
  (if (= b 0)
      a
      (gcd b (remainder a b))))

;; n以内,与n互质的正整数乘积
(define (prime-to-n-accumulate n)
  (define (filter? x)
    (= (gcd x n) 1))
  (define (next x)
    (+ x 1))
  (filtered-accumulate * 1 identity 1 next n filter?))

练习1.34

这段代码会报错,实际上最终调用了(2 2),由于2不是一个procedure,报错

练习1.35

黄金分割满足\Phi ^2 = \Phi +1,两边同除得\Phi = \Phi +\frac{1}{\Phi}

;; Exercise 1.35
;; 利用fixed-point计算黄金分割
#lang racket
(require "fixed-point.rkt")

;; 计算近似黄金分割
(define (golden-ratio)
  (fixed-point (lambda(x) (+ 1.0 (/ 1 x))) 1))

(golden-ratio)

练习1.36

;; Exercise 1.36
;; 利用fixed-point计算x^x = 1000,并打印中间结果
#lang racket

;; 设定精度
(define tolerance 0.00001)

;; fixed-point计算
(define (step-display-fixed-point f first-guess)
  (define (close-enough? v1 v2)
    (< (abs (- v1 v2)) tolerance))
  (define (try guess)
    (let ((next (f guess)))
      (display next)
      (newline)
      (if (close-enough? guess next)
          next
          (try next))))
  (try first-guess))

(step-display-fixed-point (lambda (x) (/ (log 1000) (log x))) 4)

打印出来的中间结果如下

练习1.37

a.

递归版本的如下

;; Exercise 1.37
;; 递归,近似计算无限连续小数
#lang racket

;; 计算无限连续小数
(define (cont-frac-recursive d n k)
  (define (cont-frac-recursive-iter i)
    (if (> i k)
        0
        (/ (n i)
           (+ (d i)
              (cont-frac-recursive-iter (+ i 1))))))
  (cont-frac-recursive-iter 1))

(cont-frac-recursive (lambda (i) 1.0)
                     (lambda (i) 1.0)
                     11)

测下来k=11的时候才开始得到0.61805,约等于0.6181,4位小数精度

b.

迭代版本

;; Exercise 1.37
;; 迭代,近似计算无限连续小数
#lang racket

;; 计算无限连续小数
(define (cont-frac-iterative d n k)
  (define (cont-frac-iterative-iter i res)
    (if (= i 0)
        res
        (cont-frac-iterative-iter (- i 1)
                                  (/ (n i)
                                     (+ (d i) res)))))
  (cont-frac-iterative-iter k 0))

(cont-frac-iterative (lambda (i) 1.0)
                     (lambda (i) 1.0)
                     11)

练习1.38

;; Exercise 1.38
;; 计算e-2
#lang racket
(require "cont-frac-iterative.rkt")

;; 计算d,规律为121,141,161
(define (d x)
  (if (< (remainder x 3) 2)
      1.0
      (* 2.0 (/ (+ x 1) 3))))

;; 计算e-2
(cont-frac-iterative d
                     (lambda(x) 1.0)
                     10)

练习1.39

;; Exercise 1.39
;; 近似计算tan
#lang racket
(require "cont-frac-iterative.rkt")

;; 计算近似tan
(define (tan-cf x k)
  (/ (cont-frac-iterative (lambda(y) (- (* y 2) 1.0))
                       (lambda(y) (- (* x x)))
                       k)
     (- x)))

练习1.40

;; Exercise 1.40
;; 牛顿法求解立方方程
#lang racket
(require "newtons-method.rkt")

;; 求解立方方程
(define (cubic-root a b c)
  ;; 定义cubic函数
  (define (cubic a b c)
    (lambda(x)
      (+ (* x x x)
         (* a x x)
         (* b x)
         c)))
  ;; 求解
  (newtons-method (cubic a b c) 1))

练习1.41

我天真地以为结果会是8+5=13,然而结果是21

;; Exercise 1.41
;; 执行两次某函数
#lang racket

;; 执行两次输入函数
(define (double f)
  (lambda(x) (f (f x))))

;; inc
(define (inc x)
  (+ x 1))

(((double (double double)) inc) 5)

来展开一下

(double (double double))展开为((double double) (double double))

每一个(double double)展开是(lambda(x) (double (double x)))),将一个操作进行4次,就是将第二个操作进行4次

而第二个操作将inc进行4次,4*4就是16次inc

练习1.42

;; Exercise 1.42
;; 依次执行函数
#lang racket

;; 返回复合函数
(define (compose f g)
  (lambda(x) (f (g x))))

练习1.43

;; Exercise 1.43
;; 重复执行某函数
#lang racket
(require "compose.rkt")

;; 执行n次f
(define (repeated f n)
  (define (repeated-iter n res)
    (if (= n 0)
        res
        (repeated-iter (- n 1) (compose f res))))
  (repeated-iter n identity))

练习1.44

;; Exercise 1.44
;; smooth
#lang racket
(require "repeated.rkt")

;; 定义dx
(define dx 0.00001)

;; smooth
(define (smooth f)
  (define (avg a b c)
    (/ (+ a b c) 3))
  (lambda(x) (avg (f (- x dx)) (f x) (f (+ x dx)))))

;; n次smooth
(define (n-fold-smooth f n)
  ((repeated smooth n) f))

练习1.45

代码很好写,关键在于如何根据n确定要使用average-dump的次数,虽然题目只是要求expertiment结果,网上也很多人做了,结果就是\log _2{n},我就偷懒了

;; Exercise 1.45
;; 求解n次方根
#lang racket
(require (file "../Procedures as General Methods/fixed-point.rkt"))
(require (file "../Exponentiation/fast-exp.rkt"))
(require "repeated.rkt")

;; average damp
(define (average-damp f)
  (lambda(x) (/ (+ (f x) x) 2)))

;; 根据n,求解需要进行多少次average dump才能使fixed-point收敛
(define (average-times n)
  ;; 迭代求解log2(n),p为计数器
  (define (iter res p)
    (if (> n res)
        (iter (* 2 res) (+ p 1))
        p))
  (iter 1 0))

;; 使用fixed-point进行n次方根求解
(define (nth-roots x n)
  (define (f y)
    (/ x (fast-exp y (- n 1))))
  (fixed-point ((repeated average-damp (average-times n)) f) 1.0))

为什么average damp的次数是log_2{n},参见我的另一篇博文SICP习题1.45 为什么做average damp的次数需要大于等于log2n

练习1.46

;; Exercise 1.46
;; 利用iterative improve求sqrt
#lang racket

;; 通用迭代improve
(define (iterative-improve good-enough? improve guess)
  (if (good-enough? guess)
      guess
      (iterative-improve good-enough? improve (improve guess))))

;; sqrt
(define (sqrt x)
  (define (good-enough? guess)
    (< (abs (- (* guess guess) x)) 0.0001))
  (define (improve guess)
    (/ (+ guess (/ x guess)) 2.0))
  (iterative-improve good-enough? improve x))

猜你喜欢

转载自blog.csdn.net/jiangxuege/article/details/81838333