这是我在Scala中实现合并排序的方法:
object FuncSort {
def merge(l: Stream[Int], r: Stream[Int]) : Stream[Int] = {
(l, r) match {
case (h #:: t, Empty) => l
case (Empty, h #:: t) => r
case (x #:: xs, y #:: ys) => if(x < y ) x #:: merge(xs, r) else y #:: merge(l, ys)
}
}
def sort(xs: Stream[Int]) : Stream[Int] = {
if(xs.length == 1) xs
else {
val m = xs.length / 2
val (l, r) = xs.splitAt(m)
merge(sort(l), sort(r))
}
}
}
它工作正常,似乎渐近它也很好但是它比这里http://algs4.cs.princeton.edu/22mergesort/Merge.java.html的Java实现慢得多(大约10倍)并且使用了大量内存。是否有更快的实现合并排序功能?显然,可以逐行移植Java版本,但这不是我想要的。
UPD:我已将Stream
更改为List
并将#::
更改为::
,并且排序例程变得更快,仅比Java版本慢三到四倍。但是我不明白为什么它不会因堆栈溢出而崩溃? merge
不是尾递归的,所有参数都经过严格评估......怎么可能?
答案 0 :(得分:3)
您提出了多个问题。我尝试按逻辑顺序回答它们:
你并没有真正问过这个问题,但它引出了一些有趣的观察结果。
在Stream版本中,您在 #:: merge(...)
函数中使用merge
。通常这将是一个递归调用,并可能导致堆栈溢出,以获得足够大的输入数据。但不是在这种情况下。运算符#::(a,b)
在class ConsWrapper[A]
中实现(存在隐式转换),并且是cons.apply[A](hd: A, tl: ⇒ Stream[A]): Cons[A]
的同义词。正如您所看到的,第二个参数是按名称调用,这意味着它会被懒惰地评估。
这意味着merge
返回一个新创建的cons
类型的对象,它最终会再次调用merge。换句话说:递归不会发生在堆栈上,而是发生在堆上。通常你有足够的堆。
使用堆进行递归是一种处理非常深度递归的好方法。但它比使用堆栈慢得多。所以你以递归深度交换速度。这是主要原因,为什么使用Stream
这么慢。
第二个原因是,为了获得Stream
的长度,Scala必须实现整个Stream
。但是在排序期间Stream
无论如何都必须实现每个元素,所以这不会造成太大的伤害。
当您更改Stream for List时,您确实使用堆栈进行递归。现在可能发生堆栈溢出。但是通过排序,您的递归深度通常为log(size)
,通常是基数2
的对数。因此,要对40亿个输入项进行排序,您需要大约32个堆栈帧。默认堆栈大小至少为320k(在Windows上,其他系统具有较大的默认值),这会留下很多递归,因此需要对大量输入数据进行排序。
取决于: - )
您应该使用堆栈,而不是堆来递归。您应该根据输入数据确定您的策略:
不要使用swap并使用您的缓存。如果可以,请使用可变数据结构并进行排序。我认为功能和快速排序不能很好地协同工作。要快速排序,您必须使用有状态操作(例如,对可变数组进行就地合并)。
我通常在我的所有程序上尝试这个:尽可能使用纯函数样式,但在可行的情况下对小部件使用有状态操作(例如,因为它具有更好的性能或代码只需处理大量状态并变得更多当我使用var
而不是val
时,可以更好地阅读。
答案 1 :(得分:2)
这里有几点需要注意。
首先,您没有正确考虑初始流的排序为空的情况。您可以通过修改排序中的初始检查来解决此问题,以阅读if(xs.length <= 1) xs
。
其次,流可能具有不可计算的长度(例如Strem.from(1)
),这在尝试计算其中一半(可能无限长)时会产生问题 - 您可能需要考虑使用{{ 1}}或类似的(尽管天真地使用它可以过滤掉一些可以计算的流)。
最后,定义为在流上运行的事实可能会减慢它的速度。我尝试对您的流版本的mergesort进行大量运行,而不是编写到进程列表的版本,并且列表版本的出现速度提高了大约3倍(诚然,只有一对运行)。这表明以这种方式处理流的效率低于列表或其他序列类型(Vector可能更快,或者根据引用的Java解决方案使用数组)。
那就是说,我不是时间和效率方面的优秀专家,所以其他人也许能够做出更明智的回应。
答案 2 :(得分:0)
您的实施是自上而下的合并排序。我发现自下而上的合并排序更快,与List.sorted
相当(对于我的测试用例,随机大小的随机数列表)。
def bottomUpMergeSort[A](la: List[A])(implicit ord: Ordering[A]): List[A] = {
val l = la.length
@scala.annotation.tailrec
def merge(l: List[A], r: List[A], acc: List[A] = Nil): List[A] = (l, r) match {
case (Nil, Nil) => acc
case (Nil, h :: t) => merge(Nil, t, h :: acc)
case (h :: t, Nil) => merge(t, Nil, h :: acc)
case (lh :: lt, rh :: rt) =>
if(ord.lt(lh, rh)) merge(lt, r, lh :: acc)
else merge(l, rt, rh :: acc)
}
@scala.annotation.tailrec
def process(la: List[A], h: Int, acc: List[A] = Nil): List[A] = {
if(la == Nil) acc.reverse
else {
val (l1, r1) = la.splitAt(h)
val (l2, r2) = r1.splitAt(h)
process(r2, h, merge(l1, l2, acc))
}
}
@scala.annotation.tailrec
def run(la: List[A], h: Int): List[A] =
if(h >= l) la
else run(process(la, h), h * 2)
run(la, 1)
}