在计算机程序的结构和解释一书中,有一个使用连续平方计算指数的递归程序。
(define (fast-expt b n)
(cond ((= n 0)
1)
((even? n)
(square (fast-expt b (/ n 2))))
(else
(* b (fast-expt b (- n 1))))))
现在练习1.16:
练习1.16:设计一个演化迭代求幂过程的过程,该过程使用连续的平方并使用对数步数, 和快速的一样。 (提示:使用观察结果 (b(^ n / 2))^ 2 =(b(^ 2))^ n / 2 ,与指数n和基数b一起,保持附加的状态变量a,并以产品ab ^ n从状态到状态不变的方式定义状态变换。在过程a的开始,a被认为是1,并且答案由过程结束时的a值给出。一般而言,定义从状态到状态保持不变的不变量的技术是思考迭代算法设计的有效方法。)
我花了一个星期的时间,我完全不知道如何进行这个迭代程序,所以我放弃了并寻找解决方案。我发现的所有解决方案都是:
(define (fast-expt a b n)
(cond ((= n 0)
a)
((even? n)
(fast-expt a (square b) (/ n 2)))
(else
(fast-expt (* a b) b (- n 1)))))
现在,我能理解
(fast-expt a (square b) (/ n 2)))
使用书中的提示,但是当n是奇数时我的大脑爆炸了。在递归过程中,我得到了原因
(* b (fast-expt b (- n 1))))))
的工作原理。但是在迭代过程中,它变得完全不同,
(fast-expt (* a b) b (- n 1)))))
它工作得很好,但我完全不了解如何自己解决这个问题。它似乎非常聪明。
有人可以解释为什么迭代解决方案是这样的吗?什么是解决这些类型问题的一般方法?
答案 0 :(得分:2)
这里有一个稍微不同的实现,使事情更清楚,请注意我使用一个名为(define (fast-expt b n)
(define (loop b n acc)
(cond ((zero? n) acc)
((even? n) (loop (* b b) (/ n 2) acc))
(else (loop b (- n 1) (* b acc)))))
(loop b n 1))
的帮助程序来保留原始程序的优点:
acc
这里有什么a
?它是一个用作结果的累加器的参数(在书中他们将此参数命名为acc
,IMHO acc
是一个更具描述性的名称)。所以在开始时我们将let
设置为适当的值,然后在每次迭代中我们更新累加器,保留不变量。
一般来说,这就是"技巧"为了理解算法的迭代,尾递归实现:我们传递一个额外的参数和我们到目前为止计算的结果,并在我们到达递归的基本情况时最后返回它。顺便说一下,如上所示的迭代过程的通常实现是使用命名的(define (fast-expt b n)
(let loop ((b b) (n n) (acc 1))
(cond ((zero? n) acc)
((even? n) (loop (* b b) (/ n 2) acc))
(else (loop b (- n 1) (* b acc))))))
,这完全等效,写起来更简单:
<meta name="fragment" content="!">