你如何写一个惯用的Scala Quicksort函数?

时间:2010-06-02 22:09:03

标签: scala

我最近回答了question试图在Scala中编写quicksort函数,我看到过类似下面的代码。

def qsort(l: List[Int]): List[Int] = {
  l match {
    case Nil         => Nil
    case pivot::tail => qsort(tail.filter(_ < pivot)) ::: pivot :: qsort(tail.filter(_ >= pivot))
  }
}

我的回答得到了一些建设性的批评,指出List对于quicksort来说是一个糟糕的选择,其次是上面不是尾递归。

我尝试以尾递归方式重写上述内容,但没有太多运气。是否可以编写尾递归快速排序?或者,如果没有,如何以功能性的方式完成?还有什么办法可以最大限度地提高实施效率?

4 个答案:

答案 0 :(得分:17)

几年前,我花了一些时间尝试尽可能地优化功能性快速排序。以下是我为vanilla List[A]提出的建议:

def qsort[A : Ordering](ls: List[A]) = {
  import Ordered._

  def sort(ls: List[A])(parent: List[A]): List[A] = {
    if (ls.size <= 1) ls ::: parent else {
      val pivot = ls.head

      val (less, equal, greater) = ls.foldLeft((List[A](), List[A](), List[A]())) {
        case ((less, equal, greater), e) => {
          if (e < pivot)
            (e :: less, equal, greater)
          else if (e == pivot)
            (less, e :: equal, greater)
          else
            (less, equal, e :: greater)
        }
      }

      sort(less)(equal ::: sort(greater)(parent))
    }
  }
  sort(ls)(Nil)
}

我可以使用自定义List结构做得更好。此自定义结构基本上跟踪列表的理想(或接近理想)轴心点。因此,只需访问此特殊列表属性,我就可以在恒定时间内获得更好的枢轴点。在实践中,这比选择头部的标准功能方法要好得多。

实际上,上面的内容仍然非常活泼。这是“半”尾递归(你不能做一个尾递归快速排序而不会变得非常丑陋)。更重要的是,它首先从尾端重建,因此导致中间列表比传统方法少得多。

重要的是要注意,这是在Scala中进行快速排序的最优雅或最惯用的方式,它恰好工作得非常好。编写合并排序可能会更成功,在函数式语言中实现时通常是一种更快的算法(更不用说更清晰了)。

答案 1 :(得分:5)

我想这取决于“惯用语”是什么意思。 quicksort的主要优点是非常快速的就地排序算法。所以,如果你不能就地排序,你会失去它的所有优点 - 但你仍然坚持它的 dis 优势。

所以,这里是我为Rosetta Code编写的一些代码。它仍然没有就地排序,但另一方面,它对任何新集合进行排序:

import scala.collection.TraversableLike
import scala.collection.generic.CanBuildFrom
def quicksort
  [T, CC[X] <: Traversable[X] with TraversableLike[X, CC[X]]]      // My type parameters -- which are expected to be inferred
  (coll: CC[T])                                                    // My explicit parameter -- the one users will actually see
  (implicit ord: Ordering[T], cbf: CanBuildFrom[CC[T], T, CC[T]])  // My implicit parameters -- which will hopefully be implicitly available
  : CC[T] =                                                        // My return type -- which is the very same type of the collection received
  if (coll.isEmpty) {
    coll
  } else {
    val (smaller, bigger) = coll.tail partition (ord.lt(_, coll.head))
    quicksort(smaller) ++ coll.companion(coll.head) ++ quicksort(bigger)
  }

答案 2 :(得分:4)

碰巧我最近试图解决这个问题。我希望将经典算法(即进行就地排序的算法)转换为尾递归形式。

如果您仍然感兴趣,可以在此处查看我推荐的解决方案:

Quicksort rewritten in tail-recursive form - An example in Scala

本文还包含将初始实现转换为尾递归形式的步骤。

答案 3 :(得分:0)

我做了一些尝试以纯函数风格编写Quicksort的实验。这是我得到的(Quicksort.scala):

def quicksort[A <% Ordered[A]](list: List[A]): List[A] = {
  def sort(t: (List[A], A, List[A])): List[A] = t match {
    case (Nil, p, Nil) => List(p)
    case (l, p, g) =>  partitionAndSort(l) ::: (p :: partitionAndSort(g))
  }

  def partition(as: List[A]): (List[A], A, List[A]) = {
    def loop(p: A, as: List[A], l: List[A], g: List[A]): (List[A], A, List[A]) = 
      as match {
        case h :: t => if (h < p) loop(p, t, h :: l, g) else loop(p, t, l, h :: g)
        case Nil => (l, p, g)
      }

    loop(as.head, as.tail, Nil, Nil)
  }

  def partitionAndSort(as: List[A]): List[A] = 
    if (as.isEmpty) Nil
    else sort(partition(as))

  partitionAndSort(list)
}