如何在Scala中实现尾递归快速排序

时间:2012-02-12 08:40:21

标签: algorithm scala data-structures tail-recursion

我写了一个递归版本:

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)
}

但我不知道如何将其改为尾部复原。任何人都可以给我一个建议吗?

4 个答案:

答案 0 :(得分:14)

任何递归函数都可以转换为使用堆而不是堆栈来跟踪上下文。该过程称为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))
}

当然,你需要一个非常大的结构或一个非常小的堆栈来解决非尾递归分治算法的实际问题,因为你可以处理堆栈深度为N的2 ^ N个元素。

http://blog.richdougherty.com/2009/04/tail-calls-tailrec-and-trampolines.html

<强>更新

scalaz.Trampoline是(更多)更通用结构Free的特例。它被定义为type Trampoline[+A] = Free[Function0, A]。实际上可以更一般地编写quickSort,因此它由Free中使用的类型构造函数进行参数化。此示例显示了如何完成此操作,以及如何使用相同的代码使用堆栈,堆或同时进行绑定。

https://github.com/scalaz/scalaz/blob/scalaz-seven/example/src/main/scala/scalaz/example/TrampolineUsage.scala

答案 1 :(得分:7)

尾递归要求您在每个步骤中传递已完成和待完成工作。所以你只需要在堆上封装你的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)
}

当然,这只是一个特殊的trampolining案例,通过retronym链接(你不需要向前传递函数)。幸运的是,这种情况很容易手工完成。

答案 2 :(得分:4)

我刚写了这篇文章,其中包含有关如何将Quicksort的经典实现转换为尾递归形式的分步说明:

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

我希望你觉得它很有意思!

答案 3 :(得分:1)

使用tailrec,模式匹配和隐式排序的另一个版本:

  def sort[T](list: List[T])(implicit ordering: Ordering[T]): List[T] = {
    @scala.annotation.tailrec
    def quickSort(todo: List[List[T]], accumulator: List[T]): List[T] = todo match {
      case Nil => accumulator
      case head :: rest => head match {
        case Nil => quickSort(rest, accumulator)
        case pivot :: others =>
          others.partition(ordering.lteq(_, pivot)) match {
            case (Nil, Nil) => quickSort(rest, pivot :: accumulator)
            case (Nil, larger) => quickSort(larger :: rest, pivot :: accumulator)
            case (smaller, larger) => quickSort(smaller :: List(pivot) :: larger :: rest, accumulator)
          }
      }
    }
    quickSort(List(list), Nil)
  }

  val sorted = sort(someValues)
  val reverseSorted = sort(someIntValues)(Ordering[Int].reverse)
相关问题