爱国者*_*爱国者 4 algorithm scala tail-recursion data-structures
我写了一个递归版本:
def quickSort[T](xs: List[T])(p: (T, T) => Boolean): List[T] = xs match{
case Nil => Nil
case _ =>
val x = xs.head
val (left, right) = xs.tail.partition(p(_, x))
val left_sorted = quickSort(left)(p)
val right_sorted = quickSort(right)(p)
left_sorted ::: (x :: right_sorted)
}
Run Code Online (Sandbox Code Playgroud)
但我不知道如何将其改为尾部复原.任何人都可以给我一个建议吗?
ret*_*nym 15
可以转换任何递归函数以使用堆而不是堆栈来跟踪上下文.这个过程被称为trampolining.
以下是使用Scalaz实现的方法.
object TrampolineUsage extends App {
import scalaz._, Scalaz._, Free._
def quickSort[T: Order](xs: List[T]): Trampoline[List[T]] = {
assert(Thread.currentThread().getStackTrace.count(_.getMethodName == "quickSort") == 1)
xs match {
case Nil =>
return_ {
Nil
}
case x :: tail =>
val (left, right) = tail.partition(_ < x)
suspend {
for {
ls <- quickSort(left)
rs <- quickSort(right)
} yield ls ::: (x :: rs)
}
}
}
val xs = List.fill(32)(util.Random.nextInt())
val sorted = quickSort(xs).run
println(sorted)
val (steps, sorted1) = quickSort(xs).foldRun(0)((i, f) => (i + 1, f()))
println("sort took %d steps".format(steps))
}
Run Code Online (Sandbox Code Playgroud)
当然,你需要一个非常大的结构或一个非常小的堆栈来解决非尾递归分治算法的实际问题,因为你可以处理堆栈深度为N的2 ^ N个元素.
http://blog.richdougherty.com/2009/04/tail-calls-tailrec-and-trampolines.html
UPDATE
scalaz.Trampoline是(更)一般结构的特例Free.它被定义为type Trampoline[+A] = Free[Function0, A].它实际上可以quickSort更通用地编写,因此它通过使用的类型构造函数进行参数化Free.此示例显示了如何完成此操作,以及如何使用相同的代码使用堆栈,堆或同时进行绑定.
尾递归要求您在每一步都要完成工作,包括已完成工作和待完成工作.所以你只需要在堆上封装你的work-to-do而不是堆栈.您可以将列表用作堆栈,这样就很容易了.这是一个实现:
def quicksort[T](xs: List[T])(lt: (T,T) => Boolean) = {
@annotation.tailrec
def qsort(todo: List[List[T]], done: List[T]): List[T] = todo match {
case Nil => done
case xs :: rest => xs match {
case Nil => qsort(rest, done)
case x :: xrest =>
val (ls, rs) = (xrest partition(lt(x,_)))
if (ls.isEmpty) {
if (rs.isEmpty) qsort(rest, x :: done)
else qsort(rs :: rest, x :: done)
}
else qsort(ls :: List(x) :: rs :: rest, done)
}
}
qsort(List(xs),Nil)
}
Run Code Online (Sandbox Code Playgroud)
当然,这只是一个特殊的trampolining案例,它通过retronym链接(你不需要向前传递函数).幸运的是,这种情况很容易手工完成.