【问题标题】:QuickSort Traditional vs Functional Style What Causes This Difference?QuickSort 传统风格与功能风格是什么导致了这种差异?
【发布时间】:2017-04-01 04:44:30
【问题描述】:

我正在比较用 scala 语言编写的两个代码。

package chapter01

object QuickSortScalaTime {
  def sortFunctional(xs: Array[Int]): Array[Int] = {
    if (xs.length <= 1) xs
    else {
      val pivot = xs(xs.length / 2)
      Array.concat(sortFunctional(xs filter (pivot >)), xs filter (pivot ==), sortFunctional(xs filter (pivot <)))
    }
  }

  def sortTraditionl(xs: Array[Int]) {
    def swap(i: Int, j: Int) {
      val t = xs(i);
      xs(i) = xs(j);
      xs(j) = t;
    }

    def sort1(l: Int, r: Int) {
      val pivot = xs((l + r) / 2)
      var i = l;
      var j = r;
      while (i <= j) {
        while (xs(i) < pivot) i += 1
        while (xs(j) > pivot) j -= 1
        if (i <= j) {
          swap(i, j)
          i += 1
          j -= 1
        }
      }
      if (l < j) sort1(l, j)
      if (j < r) sort1(i, r)
    }
    sort1(0, xs.length - 1)
  }
  def main(args: Array[String]): Unit = {

    val arr = Array.fill(100000) { scala.util.Random.nextInt(100000 - 1) }
    var t1 = System.currentTimeMillis
    sortFunctional(arr)
    var t2 = System.currentTimeMillis
    println("Functional style : " + (t2-t1))

    t1 = System.currentTimeMillis
    sortTraditionl(arr)
    t2 = System.currentTimeMillis
    println("Traditional style : " + (t2-t1))
  }

}

第一个块是用函数式编写的,第二个块是传统的快速排序。顺便说一下,这些例子来自奥德斯基的书。

传统和实用之间存在巨大差异。

Functional style : 450
Traditional style : 30

我只是想知道是什么导致了这种差异。我不深入了解 scala,但我最初的猜测是传统的不使用突变和任何闭包。我们可以做些什么来提高函数式样式的性能?

【问题讨论】:

  • 在 scala 中完全可以接受在本地使用可变状态来使事情变得更快。 Martin Odersky 本人在 2013 年 scala 日的主题演讲中提出了这一点 youtube.com/watch?v=kkTFx3-duc8 。因此,在现实世界中,您的 sortFunctional 方法将创建要排序的数组的副本,并在内部使用普通的就地快速排序。只要可变状态仍然局限于本地方法,它是无害的。
  • 请注意,您的测试不会强制 Java 运行时首先编译代码,因此对这些时间几乎无能为力。不过,我希望就地排序会快得多,而且由于对大型数据集进行排序非常耗时,因此它通常是优化的目标。当然,它已经在 std 库中为您完成了:scala-lang.org/api/current/index.html#scala.util.Sorting$ 有关性能的更多信息,请参阅nicholassterling.wordpress.com/2012/11/16/scala-performance

标签: scala


【解决方案1】:

嗯,你的函数排序有点错误。它有效,但它调用了xs.filter 三次!因此,在每次调用中,您都会遍历列表 3 次,而不是 1 次。

考虑这个实现:

def sort(ls: List[Int]): List[Int] = {
  ls match {
    case Nil => Nil
    case pivot :: tail => {
      val (less, greater) = tail.partition(_ < pivot)
      sort(less) ::: pivot :: sort(greater)
    }
  }
}

我不确定它会给你想要的性能,但它避免了不必要的列表遍历。

更多,您可以阅读here 描述的答案,了解使用foldLeft 的实现

【讨论】:

  • 我想知道如果你真的随机选择一个枢轴而不是选择第一个元素,你怎么能做这种优化。
【解决方案2】:

书中提到:

命令式和函数式实现具有相同的 渐近复杂度 - O(N log(N)) 在平均情况下和 O(N2) 在 最坏的情况。但是命令式实现在哪里运行 通过修改参数数组放置,功能实现 返回一个新的排序数组并保持参数数组不变。 因此,功能实现需要更多的瞬态内存 比命令式的。

传统在原始数组上就地操作,因此不进行复制,也不需要额外的内存。函数式分配一个新数组并在每次调用时复制大量数据。

【讨论】:

    【解决方案3】:

    我不知道 scala,但功能性的可能会在您每次调用它时重建数组。每个 sortFunctional 返回一个新数组,该数组使用 Array.concat 创建一个新数组。

    sortTraditionl 没有这个开销,它就地编辑数组的内容

    【讨论】:

      【解决方案4】:

      设置了 3 次过滤,就像在 scala 程序中使用函数排序所做的那样,我们可以通过遍历列表一次来划分它,如下面的算法所示。

      def quickSort(input: List[Int]): List[Int] = {
      
      /**
       * This method divides the given list into three sublists
       * using a random pivot.
       * (less than pivot, equal to pivot, greater than pivot)
       *
       * @param list
       * @return
       */
      def pivot(list: List[Int]): (List[Int], List[Int], List[Int]) = {
        val randomPivot = list(new Random().nextInt(input.length))
      
        list.foldLeft(List[Int](), List[Int](), List[Int]())( (acc, element) => {
          val (lessThanPivot, equalToPivot, greaterThanPivot) = acc
      
          element match {
            case x if x < randomPivot => (x :: lessThanPivot, equalToPivot, greaterThanPivot)
            case x if x > randomPivot => (lessThanPivot,      equalToPivot, x :: greaterThanPivot)
            case x @ _                => (lessThanPivot,  x ::equalToPivot, greaterThanPivot)
          }
        })
      }
      
      input match {
      
        case Nil => Nil
        case oneElementList @ List(x) => oneElementList
        case head :: tail => {
      
          val (lessThanPivot, equalToPivot, greaterThanPivot) = pivot(input)
      
          //step 2 & 3
          quickSort(lessThanPivot) :::
            equalToPivot           :::
            quickSort(greaterThanPivot)
      
        }
      }
      

      代码也可以在github repo 中找到。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-07-06
        • 1970-01-01
        • 2011-07-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多