【问题标题】:Find bottleneck in Scala merge algorithm在 Scala 合并算法中找到瓶颈
【发布时间】:2015-01-23 17:29:05
【问题描述】:

我正在学习 Scala,作为起点,我正在尝试编写一个合并排序算法。我对它的合并部分的性能有疑问。

我知道这个网站上还有其他实现,但我想知道为什么我的那个不能正常工作。

这是我的代码:

@tailrec
    def merge(l1:List[Int], l2:List[Int], acc:List[Int]): List[Int] = {

      if(l1.isEmpty || l2.isEmpty) l1 ++ l2 ++ acc
      else if(l1.last> l2.last) merge(l1.init, l2, l1.last :: acc)
      else  merge(l1, l2.init, l2.last :: acc)
    }

    val a1 = List(1,4,65,52151) 
    val a2 = List(2,52,124,5251,124125125)

    println(merge(a1, a2, List()))

你怎么能看到合并函数是尾递归的并且(如果我没记错的话)我正在使用的列表方法应该花费恒定的时间。

对于 100000 个元素的列表,代码变得非常慢。

【问题讨论】:

  • 正如 Didier 指出的那样,当使用 Lists 时,您希望将它们用作堆栈,即使用 headtail。您可能想尝试使用Vectors 作为练习,以查看性能差异。当然,对于对性能至关重要的代码,您可能会改用Arrays。

标签: performance algorithm scala


【解决方案1】:

lastinit 在 List 上非常昂贵:O(N)。有效的操作是headtail:O(1)。如果您不能在开始时工作,请在前面反转列表(O(N) 但只有一次,而不是在每次迭代时),或者在最后反转您的输出,但您需要在列表的开头工作。

【讨论】:

    【解决方案2】:

    查找瓶颈的最佳方法是使用分析器。我知道 netbeans 有一个免费的;如果您可以获得 jprofiler 或 yourkit,它们非常好用。在这种特定情况下,我会指出 lastinit 是 O(n),因为 List 是一个(单独的)链表。

    【讨论】:

    • 我如何知道默认方法的复杂性?有官员吗?他们的参考
    • @Donbeo 你应该能够自己弄清楚。你需要学习算法和基本的数据结构。研究学习 Big-O 表示法
    • 我对这些东西很熟悉,但我认为有时某些方法内部的标准实现会产生特定的效果。例如,我认为指向列表最后一个元素的指针存储在列表对象中,而显然这不是真的
    • 在这种情况下,Scala API 文档很清楚。对于列表,在“性能”下:“时间:列表具有 O(1) 前置和头/尾访问。大多数其他操作是对列表中元素数量的 O(n)。这包括基于索引的元素查找,长度,追加和反转。” (scala-lang.org/api/current/#scala.collection.immutable.List)
    • stackoverflow.com/a/8197826/754787 :这是我关于为什么 List 不保留其计数的旧答案,同样的原因也适用于不保留最后一个。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-26
    • 1970-01-01
    • 2013-06-18
    • 2012-01-07
    • 1970-01-01
    • 2015-06-12
    • 1970-01-01
    相关资源
    最近更新 更多