我忙于Structure and Interpretation of Computer Programs exercise 2.18。在这里,我们必须定义一个反向过程以反转列表。它应该执行以下操作:
(reverse (list 1 4 9 16 25))
;; => (25 16 9 4 1)
我想出了以下定义:
(define (reverse list)
(if (null? list)
list
(cons (reverse (cdr list)) (car list))))
;; => (mcons (mcons (mcons (mcons (mcons '() 25) 16) 9) 4) 1).
然后在a solution In中找到类似的内容:
(define (reverse items)
(if (null? (cdr items))
items
(append (reverse (cdr items))
(cons (car items) nil))))
;; => (mcons 25 (mcons 16 (mcons 9 (mcons 4 (mcons 1 '()))))).
append
和cons
之间有区别,我无法指责。
我的问题:有什么不同,为什么结果不显示为(25 16 9 4 1)
?
答案 0 :(得分:5)
简短回答:reverse
的第一个版本错误地构建了不正确的列表,第二个版本无法有效地构建正确的列表。只要我们了解append
和cons
之间的差异,我们就可以做得更好。
append
连接两个列表。如果我们使用它只是在一个列表的末尾添加一个元素,我们将完成比需要更多的工作:我们每次必须遍历整个列表 只是放置最后一个元素(参见:Schlemiel the Painter's algorithm)。因此,使用reverse
的{{1}}实现在复杂性方面可能与append
一样糟糕。
另一方面,O(n^2)
在列表的头部添加了一个元素,因为cons
的实现具有O(n)
复杂性。通常,在Scheme中,您应该尽量避免使用reverse
来构建新的输出列表,总是更喜欢append
。现在,让我们看看您的算法使用cons
返回的内容:
cons
为什么?因为要使用(reverse '(1 2 3 4 5))
=> '(((((() . 5) . 4) . 3) . 2) . 1)
构建一个正确的列表,第二个参数必须是另一个正确的列表,但是你传递了一个单独的元素。正确的实现需要一个累加器参数 - 顺便说一下,这是更有效的,因为它使用尾递归,这是你应该已经熟悉的概念,正如本书第1.2节中介绍的那样。试试这个:
cons
对于问题的最后部分:列表正在显示(define (reverse lst acc)
(if (null? lst)
acc
(reverse (cdr lst) (cons (car lst) acc))))
(reverse '(1 2 3 4 5) '())
=> '(5 4 3 2 1)
(mcons
表示m
单元格可变)因为您正在使用的语言,请尝试切换为cons
,默认使用不可变 #lang racket
单元格,并按预期打印。