我目前正在通过Hailperin,Kaiser和Knight的计划书“Concrete Abstractions”。我已经编写了如下所示的相同算法的递归和迭代版本。该算法用于对数量或事物n个时间执行操作。所以,通常,一起复制(操作n事物)。例如,我可以找到3个像这样的立方体,一起复制(* 3 3)或3个正方形 - 复制 - (* 2 3)。
递归版:
(define together-copies-of-linrec
(lambda (combine quantity thing)
(define together-iter
(lambda (combine start thing)
(if (= start quantity)
thing
(combine (together-iter combine (+ start 1) thing)
thing))))
(together-iter combine 1 thing)))
迭代版本:
(define together-copies-of-linit
(lambda (combine quantity thing)
(define together-iter
(lambda (combine start newthing)
(if (= start quantity)
newthing
(together-iter combine (+ start 1) (combine newthing thing)))))
(together-iter combine 1 thing)))
现在,我需要编写这个算法的对数时间版本,但我真的不知道从哪里开始。在这种情况下,我没有看到如何减少每个实例的操作以使其成为对数时间。
答案 0 :(得分:1)
假设combine
保证是引用透明 0 ,您可以通过将整个计算视为二叉树 1 <来编写对数时间版本/ SUP>。例如,假设将(together-copies-of * 4 3)
称为二叉树(将together-copies-of
缩写为t
):
(t * 4 3)
|
----*-----
/ \
/ \
(t * 2 3) (t * 2 3)
| |
-----*- ---*----
/ \ / \
(t * 1 3) (t * 1 3) (t * 1 3) (t * 1 3)
这里的要点是,您不需要计算(t * 1 3)
四次,而且您不需要计算(t * 2 3)
两次;你只需要计算一次。如果我们确保只计算每行的计算次数,那么我们只需要每行执行O(1)次运算。由于二叉树中的行数在元素数量上是对数的,这意味着我们可以使用O(log n)算法。
相比之下,您当前的算法如下所示:
(t * 4 3)
|
3 *
\
(t * 3 3)
|
3 *
\
(t * 2 3)
|
3 *
\
(t * 1 3)
|
3 * 3
这是什么使你的程序(两者都是)线性的:它的结构是一条大线,所以它必然需要线性时间。
以下简单程序实现了二叉树的想法。
(define together-copies-of-log
(lambda (combine quantity thing)
(if (= quantity 1)
thing
(let ((child (together-copies-of-log combine (/ quantity 2) thing)))
(combine child child)))))
由于我刚刚写了这篇文章来展示这个概念,所以它有一些不足之处:
quantity
不是2的幂,则会失败。修复这些是留给读者的练习。 :)
一些澄清的评论:
0:为什么combine
需要引用透明?如果它不是,那么将两个调用combine
更改为一个实际上可能会改变该值,因此它不会成为有效的转换。
1:为什么是二叉树,而不是三元树或任何其他树?这是因为combine
只有两个参数。如果您正在为不同的arity函数编写一个版本,那么树将具有相同的arity。