计算机程序的构造和解,计算机程序的构造与解释(SICP)习题02

Exercise 1.22. Most Lisp implementations

include a primitive

called runtime that

returns an integer that specifies the amount of time the system has

been running (measured, for example, in microseconds). The

following timed-prime-test procedure,

when called with an integer n,

prints n and checks to

see if n is prime.

If nis prime, the procedure prints three

asterisks followed by the amount of time used in performing the

test.

(define (timed-prime-test n)

(newline)

(display n)

(start-prime-test n (runtime)))

(define (start-prime-test n start-time)

(if (prime? n)

(report-prime (- (runtime) start-time))))

(define (report-prime elapsed-time)

(display " *** ")

(display elapsed-time))

Using this procedure, write a

procedure search-for-primes that

checks the primality of consecutive odd integers in a specified

range. Use your procedure to find the three smallest primes larger

than 1000; larger than 10,000; larger than 100,000; larger than

1,000,000. Note the time needed to test each prime. Since the

testing algorithm has order of growth of a4c26d1e5885305701be709a3d33442f.png(

a4c26d1e5885305701be709a3d33442f.pngn), you should expect that testing for primes

around 10,000 should take about a4c26d1e5885305701be709a3d33442f.png10 times as long as testing for primes around 1000. Do

your timing data bear this out? How well do the data for 100,000

and 1,000,000 support the a4c26d1e5885305701be709a3d33442f.pngn prediction? Is your result

compatible with the notion that programs on your machine run in

time proportional to the number of steps required for the

computation?

先把上述代码用common lisp改写

a4c26d1e5885305701be709a3d33442f.png

cl并没有runtime函数,我也没有找到返回微秒的函数,只能用get-internal-real-time,它返回的单位和具体实现有关,我用ccl返回是毫秒。

search-for-primes 函数

a4c26d1e5885305701be709a3d33442f.png

测试结果,由于计时精度是毫秒,只好用比题目中大的数来测试

a4c26d1e5885305701be709a3d33442f.png

从结果上看,用的时间大约是√10倍

Exercise

1.23.Thesmallest-divisorprocedure

shown at the start of this section does lots of needless testing:

After it checks to see if the number is divisible by 2 there is no

point in checking to see if it is divisible by any larger even

numbers. This suggests that the values used

fortest-divisorshould

not be 2, 3, 4, 5, 6,...,

but rather 2, 3, 5, 7, 9,....

To implement this change, define a

procedurenextthat

returns 3 if its input is equal to 2 and otherwise returns its

input plus 2. Modify

thesmallest-divisorprocedure

to use(next

test-divisor)instead

of(+ test-divisor

1).

Withtimed-prime-testincorporating

this modified version ofsmallest-divisor,

run the test for each of the 12 primes found in

exercise1.22.

Since this modification halves the number of test steps, you should

expect it to run about twice as fast. Is this expectation

confirmed? If not, what is the observed ratio of the speeds of the

two algorithms, and how do you explain the fact that it is

different from 2?

增加 next 和修改 find-divisor

a4c26d1e5885305701be709a3d33442f.png

测试结果

a4c26d1e5885305701be709a3d33442f.png

之前测试结果

a4c26d1e5885305701be709a3d33442f.png

从测试结果上看,比值并不等于2,会比2小一点。应该是因为并不是完全减少了一半的步骤,而是又添加了 if

判断(next方法里),增加了一点点新的步骤。

Exercise 1.24.Modify

thetimed-prime-testprocedure

of exercise1.22to

usefast-prime?(the

Fermat method), and test each of the 12 primes you found in that

exercise. Since the Fermat test

has

a4c26d1e5885305701be709a3d33442f.png(logn)

growth, how would you expect the time to test primes near 1,000,000

to compare with the time needed to test primes near 1000? Do your

data bear this out? Can you explain any discrepancy you

find?

预期应该是(square

n) 是 n 的两倍,看测试结果

a4c26d1e5885305701be709a3d33442f.png

随着n的增加,并不是简单的log

n增长,看上面代码结果,大约后一项分别为前一项的3,4,5倍。

从1.22 到

1.24,算法的增长度只是大概的预测整体走势,在不影响大趋势的走向时,会有些其他小的因素影响算法的结果,造成与预测结果的些许偏差。这里1.24由于n越来越大,可能是计算机计算大数比小数更麻烦,所以结果并没有呈现2倍增长。

算法复杂度具有指导意义,但并不是百分百精确。

Exercise 1.25. Alyssa P.

Hacker complains that we went to a lot of extra work in

writing expmod. After all, she says,

since we already know how to compute exponentials, we could have

simply written

(define (expmod base exp m)

(remainder (fast-expt base exp) m))

Is she correct? Would this procedure serve as well for our fast

prime tester? Explain.

不正确,原来的expmod方法,会把每步计算的数控制在m以内,而fast-expt会直接算平方,对于大数的计算,还是相当费时的。

Exercise 1.26. Louis

Reasoner is having great difficulty doing

exercise 1.24.

His fast-prime? test

seems to run more slowly than

his prime? test. Louis

calls his friend Eva Lu Ator over to help. When they examine

Louis's code, they find that he has rewritten

the expmod procedure to

use an explicit multiplication, rather than

calling square:

(define (expmod base exp m)

(cond ((= exp 0) 1)

((even? exp)

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

(expmod base (/ exp 2) m))

m))

(else

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

m))))

``I don't see what difference that could make,'' says Louis. ``I

do.'' says Eva. ``By writing the procedure like that, you have

transformed the a4c26d1e5885305701be709a3d33442f.png(log n) process

into a

a4c26d1e5885305701be709a3d33442f.png(n) process.''

Explain.

从线性递归变成了树形递归,原来的expmod方法把问题规模每回都减小一半(偶数情况),而现在的expmod

树形递归,两次调用分别为一半的过程,就变成n了。

Exercise 1.27.Demonstrate

that the Carmichael numbers listed in

footnote47really

do fool the Fermat test. That is, write a procedure that takes an

integernand

tests whetheranis

congruent toamodulonfor

everya<n,

and try your procedure on the given Carmichael

numbers.

a4c26d1e5885305701be709a3d33442f.png

测试结果:

a4c26d1e5885305701be709a3d33442f.png

Exercise 1.28.One

variant of the Fermat test that cannot be fooled is called

theMiller-Rabin

test(Miller

1976; Rabin 1980). This starts

froman

alternate form of Fermat's Little Theorem, which states that

ifnis

a prime number andais

any positive integer less thann,

thenaraised

to the (n-

1)st power is congruent to 1

modulon.

To test the primality of a

numbernby

the Miller-Rabin test, we pick a random

numbera<nand

raiseato

the (n-

1)st power modulonusing

theexpmodprocedure.

However, whenever we perform the squaring step

inexpmod, we check

to see if we have discovered a ``nontrivial square root of 1

modulon,'' that is,

a number not equal to 1

orn-

1 whose square is equal to 1

modulon. It is

possible to prove that if such a nontrivial square root of 1

exists, thennis

not prime. It is also possible to prove that

ifnis

an odd number that is not prime, then, for at least half the

numbersa<n,

computingan-1in

this way will reveal a nontrivial square root of 1

modulon. (This is

why the Miller-Rabin test cannot be fooled.) Modify

theexpmodprocedure

to signal if it discovers a nontrivial square root of 1, and use

this to implement the Miller-Rabin test with a procedure analogous

tofermat-test. Check your

procedure by testing various known primes and non-primes. Hint: One

convenient way to

makeexpmodsignal

is to have it return 0.

(defun miller-rabin-test (n)

(labels ((try-it (a)

(= (miller-rabin-expmod a (1- n) n) 1)))

(try-it (1+

(random (1- n))))))

(defun miller-rabin-expmod (base exp m)

(cond ((= exp 0) 1)

((evenp

exp)

(check-nontrivial-sqrt1

(miller-rabin-expmod base (/ exp 2) m) m))

(t (rem (*

base (miller-rabin-expmod base (- exp 1) m)) m))))

(defun check-nontrivial-sqrt1 (x m)

(let ((remainder (rem (square x) m)))

(if (and (/=

x 1) (/= x (1- m)) (= remainder 1))

0

remainder)))

题中说,如果n是非素数的奇数,至少有一半的数a

测试结果,对于那些能够欺骗费马检查的数,有大部分将会遇到1取模n的非平凡平方根。

(defun full-miller-rabin-counter

(n)

(let ((counter 0))

(dotimes (i n counter)

(if (= (miller-rabin-expmod i (1- n) n) 1)

(incf counter)))))

FULL-MILLER-RABIN-COUNTER

(mapcar #'full-miller-rabin-counter '(561 1105 1729 2465

6601))

(10 30 162 70 330)

而对于其它非素数的奇数,概率更高

CL-USER>

(full-miller-rabin-counter 10001)

22

CL-USER> (full-miller-rabin-counter 10002)

1

CL-USER> (full-miller-rabin-counter 10003)

18

CL-USER> (full-miller-rabin-counter 10005)

2

CL-USER> (full-miller-rabin-counter 10007)

10006

当然10007是素数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1,过程作为返回值 在1.3中我们明白了高阶函数之后,“用一个过程作为另外一个过程的返回值”则是稀松平常的事情了,比如下面的代码: (define (f x) (+ x 1)) (define (g) f) ((g) 2) 函数g没有参数,其返回值为函数f,所以((g) 2)就运算结果就是(f 2),最后运算结果为3。 上面是用一个已命名的函数作为返回结果的,相应的,也可以将一个“匿名过程”作为结果返回,这里的“匿名过程”也就是我们的Lambda表达式,所以上面的代码可以改造成: (define (g) (lambda (x) (+ x 1))) ((g) 2) 那么((g) 2)的运算结果就是((lambda (x) (+ x 1)) 2),最后运算结果为3。 2,牛顿法 学到这里,你可能需要复习一下高等数学的基本内容,包括“导数”和“微分”,高数的在线教材可以在这里找到:http://sxyd.sdut.edu.cn/gaoshu1/index.htm 关于牛顿法的介绍可以看这里:http://en.wikipedia.org/wiki/Newton%27s_method ,下面是程序: (define (close-enough? v1 v2) (< (abs (- v1 v2)) 0.000000001)) ;定义不动点函数 (define (fixed-point f first-guess) (define (try guess step-count) (let ((next (f guess))) (if (close-enough? guess next) next (try next (+ step-count 1))))) (try first-guess 0)) ;定义导数函数 (define (D f) (lambda (x dx) (/ (- (f (+ x dx)) (f x)) dx))) ;牛顿法 (define (newton g first-guess) (fixed-point (lambda (x) (- x (/ (g x) ((D g) x 0.000000001)))) first-guess)) ;平方 (define (square x) (* x x)) ;定义开方,来测试下牛顿法 (define (sq x) (newton (lambda (y) (- (square y) x)) 1.0)) (sq 5) 3,“一等公民” 这里列出了程序语言中作为“一等公民”的语言元素所具备的几个“特权”: 可以用变量命名 可以作为过程参数 可以作为过程返回结果 可以被包含在数据结构中 4,练习1.40 求三次方程 x^3 + ax^2 + bx + c 的零点。 首先,证明 函数f(x) = x^3 + ax^2 + bx + c 是“可微”的: 由可导和可微的性质知道,可导和可微互为充要条件,所以,要证可微我们可以先证可导, f ’ (x) = (x^3)’ + (ax^2)’ + (bx)’ + (c)’ = 3x^2 + 2ax + b 所以f(x)的导数存在,那么f(x)可导,其必定可微。 其次,利用“牛顿法”:如果f(x)是可微函数,那么f(x)=0的一个解就是函数(x – f(x)/df(x)的一个不动点,其中df(x)是f(x)的导数。所以我们可以轻松得到下面的代码: (define (close-enough? v1 v2) (< (abs (- v1 v2)) 0.000000001)) ;定义不动点函数 (define (fixed-point f first-guess) (define (try guess step-count) (let ((next (f guess))) (if (close-enough? guess next) next (try next (+ step-count 1))))) (try first-guess 0)) ;定义导数函数 (define (D f) (lambda (x dx) (/ (- (f (+ x dx)) (f x)) dx))) ;牛顿法 (define (newton g first-guess) (fixed-point (lambda (x) (- x (/ (g x) ((D g) x 0.000000001)))) first-guess)) ;定义cubic函数,也就是我们题目中所谓的f(x) (define (cubic a b c) (lambda (x) (+ (* x x x) (* a x x) (* b x) c))) ;随便定义几个系数 (define a 3) (define b 5) (define c 8) (define result (newton (cubic a b c) 1.0)) ;定义一个验证过程,让其验证得到的解,是否让方程成立 (define (validate x) (= 0 (+ (* x x x) (* a x x) (* b x) c))) ;输出结果 result ;验证结果 (validate result) 比如上面我们计算 x^3 + 3x^2 + 5x + 8 = 0, 其一个解为:-2.3282688556686084 .....

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值