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

发布于 2025-01-04 09:00:23 字数 417 浏览 1 评论 0原文

我写了一个递归版本:

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

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

I wrote a recursive version:

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

But I don't know how to change it into tail-recurisive. Can anyone give me a suggestion ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

为你拒绝所有暧昧 2025-01-11 09:00:24

尾递归要求您在每一步中传递已完成的工作和待完成的工作。因此,您只需将要做的工作封装在堆上而不是堆栈上。您可以将列表用作堆栈,这很简单。这是一个实现:

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

当然,这只是由追溯名链接的蹦床的特殊情况(您不需要向前传递函数)。幸运的是,这个案例很容易手工完成。

Tail recursion requires you to pass work, both completed and work-to-do, forward on each step. So you just have to encapsulate your work-to-do on the heap instead of the stack. You can use a list as a stack, so that's easy enough. Here's an implementation:

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

This is, of course, just a special case of trampolining as linked to by retronym (where you don't need to pass the function forward). Fortunately, this case is easy enough to do by hand.

星光不落少年眉 2025-01-11 09:00:24

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

以尾递归形式重写的快速排序 - Scala 中的示例

我希望您觉得它有趣!

I just wrote this article which contains step by step instructions on how to convert the classic implementation of Quicksort to tail-recursive form:

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

I hope you find it interesting!

抠脚大汉 2025-01-11 09:00:24

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

One more version using tailrec, pattern matching and implicit ordering:

  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)
小傻瓜 2025-01-11 09:00:23

任何递归函数都可以转换为使用堆(而不是堆栈)来跟踪上下文。该过程称为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< /code> 是(更)更通用的结构的特例,Free。它的定义为type Trampoline[+A] = Free[Function0, A]。实际上可以更通用地编写 quickSort,因此它由 Free 中使用的类型构造函数参数化。此示例展示了如何完成此操作,以及如何使用相同的代码使用堆栈、堆或并发进行绑定。

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

Any recursive function can be be converted to use the heap, rather than the stack, to track the context. The process is called trampolining.

Here's how it could be implemented with 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))
}

Of course, you need either a really big structure or a really small stack to have a practical problem with a non-tail-recursive divide and conquer algorithm, as you can handle 2^N elements with a stack depth of N.

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

UPDATE

scalaz.Trampoline is a special case of a (much) more general structure, Free. It's defined as type Trampoline[+A] = Free[Function0, A]. It's actually possible to write quickSort more generically, so it is parameterized by the type constructor used in Free. This example shows how this is done, and how you can then use the same code to bind using the stack, the heap, or in concurrently.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文