QuickSort传统与功能风格是什么导致这种差异?

时间:2014-01-02 10:03:43

标签: scala

我正在比较用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的深度,但我最初的猜测是传统的使用没有变异和任何闭包。我们可以做些什么来改善功能风格的表现?

4 个答案:

答案 0 :(得分:10)

嗯,你的功能排序有点不对劲。它有效,但它调用了xs.filter三次!因此,在每次通话中,您都会遍历列表三次,而不是一次。

考虑这个实现:

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的实施

答案 1 :(得分:7)

书中提到:

  

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

传统在原始阵列上就地操作,因此不会进行任何复制,也不需要额外的内存。功能部件分配新阵列并在每次呼叫时复制大量数据。

答案 2 :(得分:1)

我不知道scala,但是每次调用时,函数都可能会重建数组。每个sortFunctional返回一个新数组,该数组使用Array.concat进行连接,该数组创建一个新数组。

sortTraditionl没有这个开销,它编辑了inplace

中的数组内容

答案 3 :(得分:0)

如在具有功能类别的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。

中找到