如何编写惯用的Scala快速排序函数?

kxeu7u2r  于 2022-12-18  发布在  Scala
关注(0)|答案(5)|浏览(128)

我最近回答了一个question问题,试图用Scala编写一个快速排序函数,我在某个地方看到过类似下面代码的东西。

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是快速排序的一个糟糕的集合选择,其次,上面的方法不是尾部递归的。
我试着用尾部递归的方式重写上面的代码,但是没有太多的运气。有可能写一个尾部递归的快速排序吗?或者,如果没有,它怎么能用函数式的风格来完成呢?还有,怎样才能最大限度地提高实现的效率呢?

z0qdvdin

z0qdvdin1#

几年前,我花了一些时间尽可能地优化函数式快速排序,下面是我为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中最优雅或最惯用的快速排序方法,它只是碰巧运行得很好。您可能会在编写合并排序时取得更大的成功,在函数语言中实现合并排序通常是一个更快的算法(更不用说更干净了)。

hfyxw5xn

hfyxw5xn2#

我想这取决于你所说的“惯用”是什么意思。quicksort的主要优点是它是一个非常快的原地排序算法。所以,如果你不能原地排序,你就失去了它所有的优点--但是你仍然坚持它的缺点。
下面是我为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)
  }
vohkndzv

vohkndzv3#

碰巧我最近也尝试过解决同样的问题,我想把经典的算法(即原地排序的算法)转换成尾部递归形式。
如果您仍然感兴趣,您可以在这里看到我推荐的解决方案:
Quicksort rewritten in tail-recursive form - An example in Scala
本文还包含了我将初始实现转换为尾递归形式所遵循的步骤。

rqenqsqc

rqenqsqc4#

我做了一些实验,试图用纯函数式风格编写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)
}
ars1skjm

ars1skjm5#

我的Scala 3解决方案。

import scala.language.postfixOps
import scala.util.Random

val randomArray: Array[Int] = (for(_ <- 1 to 1000) yield Random.nextInt(1000)).toArray
    def quickSort(inputArray: Array[Int]): Array[Int] =
        inputArray.length match
            case 0 => inputArray
            case 1 => inputArray
            case _ => Array.concat(
                quickSort(inputArray.filter(inputArray(inputArray.length / 2)
                inputArray.filter(inputArray(inputArray.length / 2) ==),
                quickSort(inputArray.filter(inputArray(inputArray.length / 2)
    print(quickSort(randomArray).mkString("Sorted array: (", ", ", ")"))

相关问题