4

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 ?

Mat
  • 202,337
  • 40
  • 393
  • 406
爱国者
  • 4,298
  • 9
  • 47
  • 66
  • 5
    You can't -- the idea of the algorithm is to break the work in smaller tasks, perform them recursively and then to put the results together. Because the latter is nessecarily the last thibg you do in quicksort, it can't be tail recursive. – Ingo Feb 12 '12 at 08:45
  • 1
    This similar question but in OCaml might help: http://stackoverflow.com/questions/5634083/implementing-a-tail-recursive-version-of-quick-sort-in-f-ocaml – perelman Feb 12 '12 at 08:47
  • You can still understand the underlying algorithm explained. – Christopher Chiche Feb 12 '12 at 09:11
  • "The name "divide and conquer" is sometimes applied also to algorithms that reduce each problem to only one subproblem, such as the binary search algorithm for finding a record in a sorted list (or its analog in numerical computing, the bisection algorithm for root finding).[1] These algorithms can be implemented more efficiently than general divide-and-conquer algorithms; in particular, if they use tail recursion, they can be converted into simple loops" http://en.wikipedia.org/wiki/Divide_and_conquer_algorithm – retronym Feb 12 '12 at 11:46
  • 1
    Why bother? The quicksort algorithm only uses at most O(log n) stack frames at a time. – Dan Burton Feb 12 '12 at 18:40
  • @DanBurton because i am curious whether any recursive call can be converted to tail-recursion style – 爱国者 Sep 28 '12 at 16:00
  • @Ingo Think outside the box. Yes you've been taught the algorithm, but pretend you live in a different universe. What could be possible? Trampolining perhaps? – dlite922 Apr 13 '17 at 19:03

4 Answers4

15

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

retronym
  • 54,768
  • 12
  • 155
  • 168
  • Where does `suspend` comes from? Where is the source on github for `Trampoline`. I was not able to find a `Trampoline.scala` file. – huynhjl Feb 12 '12 at 17:00
  • https://github.com/scalaz/scalaz/blob/scalaz-seven/core/src/main/scala/scalaz/Free.scala – retronym Feb 12 '12 at 17:05
  • 2
    Naive quicksort is not guaranteed divide-and-conquer. The OP's algorithm breaks on `List.range(0,10000).reverse`, for example. – Rex Kerr Feb 12 '12 at 17:49
  • Right you are. But who would be so pathological as to use quicksort to reverse a list!? :) – retronym Feb 12 '12 at 20:24
  • 2
    Depends where the list comes from. Downward-trending data isn't that uncommon. – Rex Kerr Feb 12 '12 at 21:56
10

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.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
4

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!

  • 1
    You must disclose affiliation with external blog posts. Please also post relevant content in the answer itself - a link is no good if someday it becomes unavailable. – Beau Grantham Sep 28 '12 at 19:34
1

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)