【问题标题】:How efficient is (Guile) Scheme's reverse function(Guile) Scheme 的反向功能有多高效
【发布时间】:2018-12-19 03:41:03
【问题描述】:

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

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

OTOH 在双向链表中,只需简单地从尾到头遍历列表,这样效率会更高。

我的问题是:如果我有一个程序生成一个列表(以相反的顺序)并且在某些时候处理整个列表的情况,是否值得费心以一种可以反向处理列表的方式来实现该处理顺序,还是先简单地使用reverse不会受到惩罚?

这是针对 Guile Scheme 的,具体来说是 Guile 1.8(如果有影响的话)。

【问题讨论】:

  • "双向链表可以简单地从头到尾遍历列表,效率更高。"也就是说,假设您有一个指向最后一个元素的指针。
  • 基准测试!这篇博文定义了append 的多个版本。尝试预测哪一个在 Guile 上最快。那就试试吧。准备好感到惊讶。 scheme.dk/blog/2007/05/know-your-implementation.html
  • 您的问题中缺少的重要信息:您的列表有多长,您将反转它们多少次?如果您的列表少于 50K 元素,并且您相对不频繁地反转它们,那么您绝对不必担心。

标签: linked-list scheme guile


【解决方案1】:

反转需要 O(n) 时间来创建 n 长的反转列表,因此也需要 O(n) 空间。您只需遍历原始列表 一次 即可将其反转。

如果不再使用原始列表,则可能会对其进行垃圾收集(何时?成为问题)并回收其内存,以实现总体理论 O(1) 空间成本;但垃圾收集有其自身的成本。

所以,测试一下,如果您的列表很长,并且您看到大量垃圾收集正在进行,请按照您的建议进行操作。它会或多或少地将您的时间和空间需求减半(在代码库的那部分)。

但是,如果列表构建和反转占用了您整体时间和空间的 0.0001%,那么减半不会有太大的改进。

顺便说一下,单链表中没有反向链接。反转只是通过重复 consing 遍历原始单元格来构建新的 cons 单元格。

【讨论】:

  • 这个答案提供了有价值的解释。但是,由于代码示例,我选择了其他答案。
【解决方案2】:

使用内置的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)))))

另一方面,我不会太担心它,除非您操作的列表非常庞大,并且您的分析器证明 reverse 过程是一个瓶颈。

只需使用内置的,即可简化您的代码 - 组合现有函数是函数式编程所鼓励的风格。在Scheme中,我们尝试以tail-recursive的方式处理列表,即使这意味着创建一个反向列表并且我们需要在最后调用reverse,这是完全可以接受的。

【讨论】:

    【解决方案3】:

    想象一下简单的复制功能:

    (define (copy1 lst)
      (let loop ((lst lst) (acc '()))
        (if (null? lst)
            (reverse acc)
            (loop (cdr lst) (cons (car lst) acc)))))
    

    相对于你使用附加的地方:

    (define (copy2 lst)
      (let loop ((lst lst) (acc '()))
        (if (null? lst)
            acc
            (loop (cdr lst) (append acc (list (car lst)))))))
    

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

    现在回答你的问题。您应该使用针对您的任务优化的数据类型。如果您总是在列出要附加到末尾的列表,则反向执行列表中的所有工作并等待reverse 直到您需要它。您甚至可以围绕它进行抽象。

    【讨论】:

    • 我知道使用 cons 与 append 创建列表的含义,并且我使用的列表最终会反转。我的问题是,内置 reverse 的成本是否值得在以后找出使用反向列表的方法,而不是首先将其反向并以“真实世界”的顺序使用它。
    • @uli_1973 内置 reverse 是 O(n)。这在很大程度上取决于您如何以“真实世界”的顺序使用它。正如我在最后一部分中所写的那样,您应该在流程之后为您的数据建模。除非这对算法也有好处,否则所有硬算法都不会将其数据结构保持在视觉上令人愉悦的顺序。
    • @WillNess 是的。它有时会发生:/
    猜你喜欢
    • 2014-03-15
    • 2016-11-01
    • 2016-10-23
    • 2016-01-17
    • 1970-01-01
    • 1970-01-01
    • 2020-12-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多