码迷,mamicode.com
首页 > 其他好文 > 详细

【SICP练习】8 练习1.21-1.28

时间:2015-02-07 11:39:42      阅读:126      评论:0      收藏:0      [点我收藏+]

标签:

练习1.21

这道题几乎没有难度,除非在把书中函数写入到Edwin中时输入错误。

(smallest-divisor 199)

;Value: 199

(smallest-divisor 1999)

;Value: 1999

(smallest-divisor 19999)

;Value: 19999

 

练习1.22

这道题中需要判断素数的部分书中都已经列出来了,但要求是要找出多个素数,因此我们要有一个能够不断求素数的函数。在C等语言中我们可以通过一个for循环来轻易地求出来,在Scheme中我们完全可以用迭代来实现这一功能。另外因为是要的素数,因此完全不用考虑偶数了。于是我们先来写一个不断求奇数的函数。

(define (odd-after-x x)

       (if (odd? x)

          (+ 2 x)

          (+ 1 x)))

通过将偶数加1、奇数加2,轻易地求出了x之后的奇数。

判断素数的函数我们直接列出了:

(define (prime? n)

(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))

(= n (smallest-divisor n)))

下面我们利用这两个函数来定义一个不断产生素数的函数:

(define (prime-after-prime n count)

       (cond ((= count 0) (display “These are all prime numbers”))

                ((prime? n) (display n)

                                  (newline)

                                   (prime-after-prime (odd-after-x n) (- count 1)))

                 (else (prime-after-prime (odd-after-x n) count))))

如此一来便能够按照书中的要求算出一千、一万等的3个最小的素数了,不过还不能计算所需时间。而要求时间,我们可以用real-time-clock,这在【Scheme归纳】7中有相关介绍。下面我们就来将计算时间的功能加入进去。

(define (get-time&prime n)

       (let ((start-time (real-time-clock)))

         (prime-after-prime n 3)        由于题目中只要求求出3个最小的素数即可。

         (- (real-time-clock) start-time)))

下面我们就来开始测试了:

(get-time&prime 1000)

1009

1013

1019

These are all prime numbers

;Value: 0

时间太短记为0了我也没办法,下面再来几组大的。

(get-time&prime 10000)

10007  

10009

10037

These are all prime numbers

;Value: 0

还是0,莫非函数错了?再来一次。

(get-time&prime 100000)

100003

100019

100043

These are all prime numbers

;Value: 0

我也不知道为什么会这样了,用一百万测试的还是0,难度是i7的速度快?不过:

(get-time&prime 10000000)

10000019

10000079

10000103

These are all prime numbers

;Value: 15

继续,一亿用了47微秒,10亿用了157微秒。100亿、一千亿、一万亿用了672、1750、5156。一亿亿用了621890秒,3个最小的素数分别是10000000000000061、10000000000000069、10000000000000079。

后来在网上也看到相同的函数,而对方评论下说对方写错了。我再看看书中的内容发现果然如此,我也看错了,第36页上面说的是检查每个素数所需要的时间,应该是意味着每个素数都要有个时间吧?如是将函数改成了这样:

(define (get-time&prime n)

   (let ((start-time (real-time-clock)))

      (define (prime-after-prime n count)

      (cond ((= count 0) (display "These are all prime numbers."))

                ((prime? n) (display n)

                              (display (- (real-time-clock) start-time))

                         (newline)

                             (prime-after-prime (odd-after-x n) (- count 1)))

                 (else (prime-after-prime (odd-after-x n) count)))))

      (prime-after-prime n 3)))

因为觉得返回更多的素数结果会更加准确,于是我在测试的时候将最后一行代码的3改成了5。通过测试,在1000和10000中的每个素数的计算时间都是0。10万和100万也都是零。我觉得是因为这本书的历史比较久远了,当时的硬件比不上现在的了。于是我又用了一千万来测试,返回的5个时间分别是0、15、15、15、31,用一亿来测试返回的5个时间分别是15、31、47、62、78,和根号10的差距不是太遥远吧。十亿返回的5个时间分别是47、94、141、297、391。至此这道习题就算做完了,稍有不足都请读者列出。

 

练习1.23

首先我们按照题目要求来写出相应的next函数,然后再修改find-divisor函数。

(define (next x)

       (if (= x 2)

          3

          (+ n 2)))

(define(find-divisor n test-divisor)

       (cond ((> (square test-divisor) n) n)

             ((divides? test-divisor n) test-divisor)

             (else (find-divisor n (next test-divisor)))))

然后重新编译之前写好的get-time&prime函数,再加以测试这道题就算完成了。

 

练习1.24

我们先将书中已给出的代码写入Edwin中。

(define (fermat-test n)

    (define (try-it a)

        (= (expmod a n n) a))

    (try-it (+ 1 (random (- n 1)))))

(define (fast-prime? n times)

    (cond ((= times 0) true)

          ((fermat-test n) (fast-prime? n (- times 1)))

          (else false)))

(define (expmod base exp m)

    (cond ((= exp 0) 1)

          ((even? exp) (remainder (square (expmod base (/ exp 2) m)) m))

          (else (remainder (* base (expmod base (- exp 1) m)) m))))

于是就有了一个新的prime?函数如下:

(define (prime? n)

    (fast-prime? n 100))

然后载入上一题中的get-time&prime函数,如果已经在上一题中保存了起来现在就可以直接load了。然后经过一番测试后,结论很明显咯。练习1.22中的get-time&prime函数的复杂度为Θ(√n),而本题中的复杂度为Θ(logn) 。

 

练习1.25

这道题由Alyssa的一个另一版本的expmod来引出,这个expmod的功能和之前的一样的。但是之前版本的expmod每次都有一个remainder来讲乘幂控制在一个不大的范围内,这样通过不断的迭代,将很大的数字分解开来得以加快计算速度。而题目中这一版本的expmod则只通过了一次remainder。大家可以用2个非常大的数字来测试一番,比如几百亿之类的。

 

练习1.26

这本书的练习好像很多都和某个人有关,不愧是一本经典著作,通过MIT大量的修修补补。下面我们进入正题吧,Louis的问题就在于计算了2次(expmod base (/ exp 2) m),如果是用的square,则只会计算一次。更何况在这多余的一次里,又是一个漫长的迭代。Lisp最吸引我的就是它的精巧优美了,能短则短吧。

 

练习1.27

这道题的场景是在注释47中,博主更关心的问题是:(第三行)撞上能欺骗费马检查的值的概率有多少,居然会比什么宇宙射线造成计算机出差。后者个人感觉是永远不会发生的,前者倒是还有可能发生。希望把前者的概率算出来的童鞋将过程列出来啦!

言归正传,题目的意思就是要去验算注释47中的那几个Carmichael,那就来code吧:

(define (find-carmichael n)

   (define (find-carmichael-test x n)

       (cond ((= x n) #t)

              ((same-remainder? x n)

              (find-carmichael-test (+ x 1) n))

       ((else #f)))

   (define (same-remainder? x n)

       (= (expmod x n n) x))

 (find-carmichael-test 1 n))

当然了,这里要load保存好的expmod函数。然后就是测试了:

(carmichael-test 6601)

;Value: #t

其他的都一样的返回结果,不然就是函数写错了。

 

练习1.28

这道题主要分为三个部分:

1、非平凡平方根,并添加到expmod函数中

2、类似于fermat-test的过程

3、通过已知的素数和非素数来检验

下面我们首先来写出能够在遇到非平凡平方根的时候报错的函数,在这个函数中:当x不等于1,x不等于(n-1),并且x的平方对n取余等于1,这三个条件都为真时则可以说遇到了“1取模n的非平凡平方根”。下面是该函数:

(define (not-square-root? x n)

(and (not (= x 1))

     (not (= x (- n 1)))

     (= 1 (remainder (square x) n))))

然后我们要将这个函数添加到expmod中,在cond里面添加一项即可:

(define (expmod base exp m)

    (cond ((= exp 0) 1)

          ((not-square-root? base m) 0)

          ((even? exp)

             (remainder (square (expmod base (/ exp 2) m)) m))

      (else (remainder (* base (expmod base (- exp 1) m)) m))))

第一步我们已经完成了,下面来看看第二步。在fermat-test中,已经有了一个try-it函数,但这个函数在这道题里不适用,因此我们来自己写一个产生随机数的函数。这个函数用来生成大于0并且小于n的随机数。

(define (zero-to-n-random x)

       (let ((r (random x)))

      (if (not (= r 0))

          r

              (zero-to-n-random x))))

random并不会参数负数的随机数,也不能用负数作为参数来产生随机数。下面我们来继续完成miller-rabin-prime函数。

(define (miller-rabin-prime? n)

    (let ((x (ceiliing (/ n 2))))

        (miller-rabin-test n x)))

(define (miller-rabin-test n x)

    (cond ((= x 0) #t)

          ((= (expmod (zero-to-n-random n) (- n 1) n) 1)

       (miller-rabin-test n (- x 1)))

          (else #f)))

最后还剩下测试的工作了:

(miller-rabin-prime? 1729)

;Value: #f

(miller-rabin-prime? 2821)

;Value: #f

(miller-rabin-prime? 31)

;Value: #t

【SICP练习】8 练习1.21-1.28

标签:

原文地址:http://www.cnblogs.com/NoMasp/p/4278450.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!