【问题标题】:How to implement tail-recursive quick sort in Scala如何在 Scala 中实现尾递归快速排序
【发布时间】:2012-02-12 08:40:21
【问题描述】:

我写了一个递归版本:

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

但我不知道如何将其更改为尾递归。谁能给我一个建议?

【问题讨论】:

  • 你不能——算法的想法是把工作分解成更小的任务,递归地执行它们,然后把结果放在一起。因为后者必然是你在快速排序中做的最后一个事情,所以它不能是尾递归的。
  • 这个类似的问题,但在 OCaml 中可能会有所帮助:stackoverflow.com/questions/5634083/…
  • 解释的底层算法还是可以理解的。
  • "“分而治之”这个名称有时也用于将每个问题简化为仅一个子问题的算法,例如用于在排序列表中查找记录的二分查找算法(或其类似物数值计算,求根的二分算法)。[1]这些算法可以比一般的分治算法更有效地实现;特别是,如果它们使用尾递归,它们可以转换成简单的循环“en.wikipedia.org/wiki/Divide_and_conquer_algorithm
  • 何必呢?快速排序算法一次最多只使用 O(log n) 个堆栈帧。

标签: algorithm scala data-structures tail-recursion


【解决方案1】:

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

【讨论】:

  • suspend 来自哪里? Trampoline 的 github 源在哪里。我找不到Trampoline.scala 文件。
  • 天真的快速排序不能保证分而治之。例如,OP 的算法在 List.range(0,10000).reverse 上中断。
  • 没错。但是谁会如此病态以至于使用快速排序来反转列表!? :)
  • 取决于列表的来源。下降趋势的数据并不少见。
【解决方案2】:

尾递归要求您在每一步都向前传递工作,包括已完成的工作和待做的工作。因此,您只需将待办事项封装在堆上而不是堆栈上。您可以将列表用作堆栈,这很容易。这是一个实现:

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

当然,这只是一个特例的蹦床,与retronym 相关联(您不需要将函数向前传递)。幸运的是,这种情况很容易手工完成。

【讨论】:

    【解决方案3】:

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

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

    希望你觉得有趣!

    【讨论】:

    • 您必须披露与外部博客帖子的从属关系。还请在答案本身中发布相关内容 - 如果有一天链接不可用,则链接无效。
    【解决方案4】:

    另一个使用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)
    

    【讨论】:

      猜你喜欢
      • 2017-01-26
      • 2019-11-16
      • 1970-01-01
      • 2017-05-25
      • 2023-03-30
      • 1970-01-01
      • 1970-01-01
      • 2020-08-14
      • 1970-01-01
      相关资源
      最近更新 更多