quicksort

quicksort

Robbert Haarman

2006-09-19


Introduction

This program implements the quicksort algorithm for integers. It demonstrates recursion, allocating sequences, iterating over sequences, and concatenating sequences.

Quicksort is a divide and conquer algorithm, meaning it recursively partitions the problem into smaller problems until they are trivially solved. The trivially solved case is them empty sequence, which is sorted. To sort a non-empty sequence, quicksort selects one element of the sequence as a pivot. It then partitions the sequence into three new sequences: a left sequence containing all items from the original sequence that are smaller than the pivot, a right sequence containing all items that are larger, and a center sequence containing all items that are equal to the pivot. The left and right sequences are guaranteed to be smaller than the original sequence. The center sequence is guaranteed to be sorted.

After the partitioning, the sorted sequence is obtained by sorting the left sequence, sorting the right sequence, and then concatenating the three sorting sequences in the order left, center, right. The resulting sequence is returned.


C

In C, the implementation looks as follows:

#include <stdio.h>

int *quicksort(int *items, int size) {
	int left[size], center[size], right[size];
	int i, n, pivot, left_size = 0, center_size = 0, right_size = 0;

	if(size <= 0 || !items) return NULL;

	pivot = items[0];
	for(i = 0; i < size; i++) {
		n = items[i];
		if(n < pivot) left[left_size++] = n;
		else if(pivot < n) right[right_size++] = n;
		else center[center_size++] = n;
	}

	quicksort(left, left_size);
	quicksort(right, right_size);

	memcpy(items, left, left_size * sizeof(int));
	memcpy(items + left_size, center, center_size * sizeof(int));
	memcpy(items + left_size + center_size, right,
		right_size * sizeof(int));

	return items;
} 

int main(int argc, char **argv) {
	int i;
#define SIZE 5
	int items[SIZE] = { 1, 3, 5, 4, 2 };

	quicksort(items, SIZE);

	for(i = 0; i < SIZE; i++) printf("%d\n", items[i]);

	return 0;
}

A peculiarity of this implementation is that the quicksort function takes a size parameter, in addition to the array that is to be sorted. This is necessary, because C only stores the element of arrays, not their sizes. Since we need to know how many elements are in the array, we must pass this in as an extra parameter.

The space for left, center, and right is allocated on the stack, which means that it will be de-allocated automagically when quicksort returns. To make sure there is enough space in each of them, they are all made equally large as items. This wastes quite some space. Perhaps a better solution would be to use dynamic memory allocation, but that is somewhat cumbersome in C.

In the last step before returning, quicksort overwrites items with the sorted values. This side effect may be undesired in many situations; in such cases, the items to be sorted should be copied into a newly allocated array before calling quicksort.

This implementation contains 127 words.


OCaml

In OCaml, the quicksort algorithm can be implemented like this:

open Printf

let rec quicksort items =
	match items with
		  [] -> []
		| x::xs -> quicksort_rec xs x [] [x] []
and quicksort_rec items pivot left center right =
	match items with
		  [] -> (quicksort left) @ center @
			(quicksort right)
		| x::xs ->
			if x < pivot then
				quicksort_rec xs pivot
					(x::left) center right
			else if pivot < x then
				quicksort_rec xs pivot left
					center (x::right)
			else quicksort_rec xs pivot left
				(x::center) right

let _ = List.iter (printf "%d\n") (quicksort [1; 3; 5; 4; 2])

This implementation demonstrates several features of OCaml. First of all, pattern matching.

match items with
	  [] -> …
	| x::xs -> …

first compares items to [] (the empty list). If this results in a match, the first … is evaluated and returned. Otherwise, matching continues with the clause after the next |, which is x::xs. This uses list construction syntax (x::xs returns the list resulting from prepending the element x to the list xs) to match a list with at least one element (xs could match the empty list). It evaluates the … after the arrow with x bound to the first element of the list and xs bound to the rest of the list.

Secondly, mutually recursive functions. OCaml normally requires that bindings be defined before they are used. This poses a problem for quicksort and quicksort_rec, because they call one another, and thus would both have to be defined first. The let rec … and … takes care of this, by allowing all defined bindings to see one another. More bindings can be defined by adding more and clauses.

Finally, note that (printf "%d\n") calls printf with one argument fewer than it requires. This is called currying (after Haskell Curry). The result is a new function, which will complete the specified function call once the remaining arguments are supplied. Thus, (printf "%d\n") returns a function that takes an integer argument and prints it, followed by a newline.

This version has 87 words.


Ruby

The following implements quicksort in Ruby:

def quicksort items
	return items if items.empty?
	left = []
	center = []
	right = []
	pivot = items[0]
	items.each do |item|
		if item < pivot
			left << item
		elsif pivot < item
			right << item
		else
			center << item
		end
	end
	quicksort(left) + center + quicksort(right)
end

puts quicksort([1,3,5,4,2])

Again, we see the each method in conjunction with a block to iterate over items. We see the infix operator << to append an item to an array, and the + operator for concatenating arrays.

The program consists of 50 words.


Common Lisp

For Common Lisp, we have the following implementation:

(defun quicksort (items)
	(if (null items) items
		(loop for x in items
			with pivot = (car items)
			with left = '()
			with center = '()
			with right = '()
			do (cond
				((< x pivot) (push x left))
				((< pivot x) (push x right))
				(t (push x center)))
			finally (return (nconc
					(quicksort left) center
					(quicksort right))))))
	
(format t "~{~A~%~}" (quicksort '(1 3 5 4 2)))

Again, we see the Loop Facility being used. A few other noteworthy things: (null list) tests if list is the empty list. (push item list) prepends item to the list stored in list, and stores the updated list in the same place. (nconc …) concatenates the lists it is passed and returns the result. Both of these are destructive, meaning they modify their arguments.

(cond …) takes a number of clauses of the form (test expr …). It evaluates each test in order until one test passes, then evaluates the expr … for that clause, returning the result of the last expr evaluated. If no test passed, cond returns nil. t is Common Lisp's true value, which means the last clause in this cond form will be executed if none of the others are.

This program also sports an interesting format string: "~{~A~%~}". The ~{ takes the next argument to format, which must be a list, and applies everything between ~{ and ~} to each element of that list. In this case, the elements of the list are printed (aestetically), with a newline after each one.

The program contains 64 words.

Valid XHTML 1.1! Valid CSS! Viewable with Any Browser