quicksort

C. A. R.'s quicksort algorithm can be applied either to lists or to vectors, and either to create a new, sorted structure without modifying the old one or to rearrange the elements of the given structure. All of these possibilities in the two implementations shown here: a destructive version applying to a vector and a non-destructive one applying to a list.

As usual, the objective in either case is to obtain a structure in which the elements are arranged in a monotonically nondecreasing order (according to an optionally specified total ordering, defaulting to <).

First, here's the destructive version for vectors. It uses the same internal partition! procedure as the algorithm for finding the kth-least element of a vector. The body of the main procedure is a recursion, controlled by the parameters start and stop, designating the leftmost and rightmost positions in the part of the vector to be sorted. If they are equal, or if the value of stop is less than that of start, there's nothing to do. Otherwise, a pivot is selected (it's the element in the stop position), the rest of the subvector is partitioned around it, and the pivot is swapped into place. This divides the subvector into two smaller subvectors, which are then sorted separately -- the smaller one first, to minimize the amount of storage needed for the recursion.

(define quicksort!
  (lambda (vec . opt)
    (let* ((precedes? (if (null? opt) < (car opt)))

           (partition!
            (lambda (start stop pivot)
              (letrec ((rightwards
                        (lambda (current)
                          (if (and (< current stop)
                                   (precedes? (vector-ref vec current)
                                              pivot))
                              (rightwards (+ current 1))
                              current)))
                       (leftwards
                        (lambda (current)
                           (if (or (< current start)
                                   (precedes? (vector-ref vec current)
                                              pivot))
                               current
                               (leftwards (- current 1))))))

                (let loop ((left-pointer (rightwards start))
                           (right-pointer (leftwards (- stop 1))))
                  (if (< left-pointer right-pointer)
                      (begin
                        (vector-swap! vec left-pointer right-pointer)
                        (loop (rightwards (+ left-pointer 1))
                              (leftwards (- right-pointer 1))))
                      left-pointer))))))

      (let qs ((start 0)
               (stop (- (vector-length vec) 1)))
        (if (< start stop)
            (let* ((pivot (vector-ref vec stop))
                   (break (partition! start stop pivot)))
              (vector-swap! vec break stop)
              (if (<= (- break start)
                      (- stop break))
                  (begin
                    (qs start (- break 1))
                    (qs (+ break 1) stop))
                  (begin
                    (qs (+ break 1) stop)
                    (qs start (- break 1))))))))))
And here is the non-destructive version for lists. Like the other, it uses an internally defined procedure to perform the partitioning -- this time a non-destructive procedure that simply traverses its lists argument and prepends each element it encounters to one of two accumulators, one for elements that precede the pivot and the other for elements that do not. At the end of the list, it returns a pair consisting of the two accumulators.

The main procedure first checks whether it has been given a list of length 0 or 1; if not, it adopts the first element as a pivot and partitions the rest of the list around that pivot. The partition procedure returns a pair of smaller lists; the main procedure causes both of them to be sorted through recursive calls, then reunites the pieces with cons and append, forming a list that begins with the (now sorted) sublist of values smaller than the pivot, then has the pivot in the middle, followed by the (now sorted) sublist of values larger than the pivot.

(define quicksort
  (lambda (ls . opt)
    (let* ((precedes? (if (null? opt) < (car opt)))

           (partition
            (lambda (ls pivot)
              (let loop ((rest ls)
                         (smalls '())
                         (larges '()))
                (if (null? rest)
                    (cons smalls larges)
                    (let ((fore (car rest))
                          (aft (cdr rest)))
                      (if (precedes? fore pivot)
                          (loop aft (cons fore smalls) larges)
                          (loop aft smalls (cons fore larges)))))))))

      (let qs ((rest ls))
        (if (or (null? rest)
                (null? (cdr rest)))
            rest
            (let* ((pivot (car rest))
                   (parts (partition (cdr rest) pivot))
                   (smalls (car parts))
                   (larges (cdr parts)))
              (append (qs smalls)
                      (cons pivot (qs larges)))))))))


This document is available on the World Wide Web as

http://www.math.grin.edu/~stone/events/scheme-workshop/quicksort.html


created July 16, 1995
last revised June 25, 1996

John David Stone (stone@math.grin.edu)