使用Scheme的reverse
函数非常容易,例如,在使用(cons new-obj my-list)
而不是(append my-list (list new-obj))
反向创建列表之后。
但是,我想知道 的效率如何。如果Scheme列表是一个单个链接列表(如我假设的那样),则意味着至少要遍历整个列表 一次才能到达最后一个元素,不是吗?这就需要reverse
以某种方式创建反向链接吗?
双向链接列表中的OTOH可以简单地从头到尾遍历该列表,这样会更有效率。
我的问题是:如果我遇到一种情况,程序会生成一个列表(以相反的顺序),并在某个时刻处理整个列表,那么值得麻烦的是以一种可以反向处理列表的方式来实现该处理。命令,还是先简单地使用reverse
不会带来任何惩罚?
这是针对Guile计划的,而Guile 1.8是具体的(如果有所不同)。
答案 0 :(得分:3)
反转需要 O(n)时间来创建 n 个长的反转列表,因此也需要 O(n)空间。您只需遍历原始列表一次即可将其反转。
如果不再使用原始列表,则可能会被垃圾回收(当成为问题时),并收回其内存,以节省理论上的整个 O(1)空间成本;但是垃圾收集有自己的成本。
因此,进行测试,如果列表很长,并且看到大量垃圾正在进行,请执行建议。它将或多或少地减少您的时间和空间需求(在代码库的该部分中)。
但是,如果列表构建和反向操作占用了整个时间和空间的 0.0001%,那么将其减半并不会带来太大的改善。
顺便说一句,单链接列表中没有反向链接。反转通过反复的 cons
来构建新的 cons
单元格,从而遍历原始单元格。
答案 1 :(得分:1)
使用内置make -v
程序会受到惩罚,该程序的时间复杂度为reverse
,空间复杂度为O(n)
,看起来与此类似(是的,我们必须遍历列表直到结尾,并一路创建反向链接):
O(1)
另一方面,除非您要处理的列表过于庞大,并且您的分析器表明(define (reverse lst)
(let loop ((lst lst) (acc '()))
(if (null? lst)
acc
; notice the tail recursion!
(loop (cdr lst) (cons (car lst) acc)))))
过程是瓶颈,否则我不会对此太担心。
只需使用内置函数即可简化您的代码-编写现有函数是函数式编程所鼓励的样式。在Scheme中,我们尝试以tail-recursive的方式处理列表,即使这意味着要反向创建列表,并且我们需要在末尾调用reverse
,这完全可以接受。
答案 2 :(得分:0)
想象一下简单的复制功能:
(define (copy1 lst)
(let loop ((lst lst) (acc '()))
(if (null? lst)
(reverse acc)
(loop (cdr lst) (cons (car lst) acc)))))
与使用append的地方相对:
(define (copy2 lst)
(let loop ((lst lst) (acc '()))
(if (null? lst)
acc
(loop (cdr lst) (append acc (list (car lst)))))))
append
和reverse
均为O(n),这意味着它们会迭代整个列表。副本的两个版本之间的区别在于,带有reverse
的版本在最后一次对所有元素执行一次,而append
版本对每个元素调用append
。这使得最后一个版本O(n * n)的效率要比第一个版本O(n)低得多。如果列表很大,您会很快注意到这种差异。
现在回答您的问题。您应该使用针对任务优化的数据类型。如果您总是在列表的末尾添加内容,那么请对列表进行所有相反的操作,然后等待reverse
直到需要它为止。您甚至可以对其进行抽象。