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.
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.(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))))))))))
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