按照某某爱折腾同学的显示乘法,我们对过程进行如下调整
#lang racket
(define (runtime) ( current-inexact-milliseconds ))
(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))))
(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) #t)
((fermat-test n) (fast-prime? n (- times 1)))
(else #f)))
(define (timed-prime-test n)
(start-prime-test n (runtime)))
(define (start-prime-test n start-time)
(if (fast-prime? n 1000)
(begin (report-prime n (- (runtime) start-time)) #t) #f))
(define(report-prime n elapsed-time)
(newline)
(display n)
(display " usetime ")
(display elapsed-time))
(define (search-for-primes n count)
(cond((= count 0) n )
((timed-prime-test n) (search-for-primes (+ n 1) (- count 1)))
(else (search-for-primes (+ n 1) count))))
(search-for-primes 10 3)
(search-for-primes 100 3)
(search-for-primes 1000 3)
(search-for-primes 10000 3)
(search-for-primes 100000 3)
其实这个问题是之前书上说的正则序和应用序的问题,显示调用的方式,虽然每次的exp会降低一倍,但是显示的调用(expmod base (/ exp 2)m )会增加一次调用次数,所以两次就抵消了。但是如果我们把他写成square过程调用,因为解释器是应用序的,所以每次就只会计算一次递归算法。其实这也提醒我们在写递归算法的时候,避免方法体里面调用了两次方法体本身,这对效率影响是非常大的。
//老师的代码
(square (expmod base (/ exp 2)m ))
//某某同学的代码
(* (expmod base (/ exp 2)m ) (expmod base (/ exp 2)m ))
最后附上运行结果,基本符合θ(n) 的增长。
11 usetime 0.978271484375
13 usetime 1.951904296875
17 usetime 2.92895507812518
101 usetime 14.62939453125
103 usetime 15.623046875
107 usetime 13.668701171875108
1009 usetime 143.536376953125
1013 usetime 145.472900390625
1019 usetime 185.50292968751020
10007 usetime 1679.29541015625
10009 usetime 1637.31298828125
10037 usetime 1753.49926757812510038
100003 usetime 15371.418212890625
100019 usetime 14719.226806640625
100043 usetime 14364.817626953125100044