(Guile)Scheme的反向功能效率如何

时间:2018-07-11 07:34:35

标签: linked-list scheme guile

使用Scheme的reverse函数非常容易,例如,在使用(cons new-obj my-list)而不是(append my-list (list new-obj))反向创建列表之后。

但是,我想知道 的效率如何。如果Scheme列表是一个单个链接列表(如我假设的那样),则意味着至少要遍历整个列表 一次才能到达最后一个元素,不是吗?这就需要reverse以某种方式创建反向链接吗?

双向链接列表中的OTOH可以简单地从头到尾遍历该列表,这样会更有效率。

我的问题是:如果我遇到一种情况,程序会生成一个列表(以相反的顺序),并在某个时刻处理整个列表,那么值得麻烦的是以一种可以反向处理列表的方式来实现该处理。命令,还是先简单地使用reverse不会带来任何惩罚?

这是针对Guile计划的,而Guile 1.8是具体的(如果有所不同)。

3 个答案:

答案 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)))))))

appendreverse均为O(n),这意味着它们会迭代整个列表。副本的两个版本之间的区别在于,带有reverse的版本在最后一次对所有元素执行一次,而append版本对每个元素调用append。这使得最后一个版本O(n * n)的效率要比第一个版本O(n)低得多。如果列表很大,您会很快注意到这种差异。

现在回答您的问题。您应该使用针对任务优化的数据类型。如果您总是在列表的末尾添加内容,那么请对列表进行所有相反的操作,然后等待reverse直到需要它为止。您甚至可以对其进行抽象。