Looking for an edge with the classic Quicksort algorithm
Smart Sort
If you wanted to assign a flavor to the Quicksort algorithm, it would be sweet and sour. Sweet, because it is very elegant; sour, because typical implementations sometimes leave more questions than they answer.
The Quicksort sorting algorithm has been around for 60 years, and, if implemented properly, it is still the fastest option for many sorting tasks. According to the description on Wikipedia, a well designed Quicksort is "…somewhat faster than Merge sort and about two or three times faster than Heapsort."
Many Linux users today have studied Quicksort at some point in the past, through a computer science class or other training scenario, but unless you are working as a professional programmer, chances are it has been a few years since you have taken the time to ponder the elegant Quicksort algorithm. Still, sorting goes on all the time on Linux networks. You don't have to be a fulltime app developer to conjure up an occasional script to rank results or order a set of values extracted from a log file. This article explores some of the nuances of the classic Quicksort.
Quicksort ABC
The Quicksort [1] algorithm originated with Tony Hoare [2], who first developed it in 1959 and published it in 1961. Quicksort is what is known as a divideandconquer algorithm. One element in the array is chosen to be the pivot element. All elements smaller than the pivot element are then grouped in a subarray before it, and all elements larger than the pivot element are placed in a subarray after it. This process is then repeated with the subarrays: a pivot element is chosen, with smaller elements placed in a subarray before and larger elements placed in a subarray after. After a finite number of steps, the size of the subarrays becomes one, and at that point, the whole array has been sorted.
Too complicated? Figure 1 sums up the Quicksort algorithm. Boxes containing only one red number are already in the right position. In the first row, the number 6 is the pivot element. Now all of the elements are sorted in relation to 6. In the second row, the 5 and the 9 act as the new pivot elements. Now the partial arrays are sorted relative to 5 and 9. The result is the third row, where almost all of the elements are already sorted. Only the 8 (the new pivot element) has to swap places with the 7. If you would prefer an animated clip of the Quicksort algorithm, check out the Quicksort page on Wikipedia [1].
If the array contains n elements, an average of n*log(n) sorting steps are needed. The log(n) factor results from the fact that the algorithm halves the array in each step. In a worstcase scenario, Quicksort requires n*n sorting steps. To illustrate this worstcase scenario, consider that, in Figure 1, the middle element served as the pivot element. But any other element could also serve as the pivot element. If the first element is the pivot element and the array is already sorted in ascending order, the array must be halved exactly n times, which would be the (very unlikely) worst case. Note that a few lines of text are all I needed to describe the elegant and highly efficient Quicksort algorithm, along with its performance characteristics.
First Encounter
The classic Quicksort implementation in C lacks charm and is quite successful at disguising its elegant design, as Listing 1 demonstrates. I won't provide a full description of the code; however, one observation is very interesting.
Listing 1
Quicksort in C
01 void quickSort(int arr[], int left, int right) { 02 int i = left, j = right; 03 int tmp; 04 int pivot = arr[abs((left + right) / 2)]; 05 while (i <= j) { 06 while (arr[i] < pivot) i++; 07 while (arr[j] > pivot) j; 08 if (i <= j) { 09 tmp = arr[i]; 10 arr[i] = arr[j]; 11 arr[j] = tmp; 12 i++; j; 13 } 14 } 15 if (left < j) quickSort(arr, left, j); 16 if (i < right) quickSort(arr, i, right); 17 }
In Lines 9 to 11, the code overwrites the existing elements. Thus, the algorithm runs inplace and assumes mutable data. A nice expression has been established for the task of overwriting old values with new ones in functional programming: destructive assignment [3]. This takes us neatly to the next topic: in functional programming languages like Haskell, Quicksort is represented in a far more elegant way.
Second Encounter
In Haskell, data is immutable, which precludes destructive assignment by design. The Haskellbased Quicksort algorithm in Listing 2 creates a new list in each iteration, rather than acting directly on the array as in Listing 1. Quicksort in two lines? Is that all there is to it? Yes.
Listing 2
Quicksort in Haskell
qsort [] = [] qsort (x:xs) = qsort [y  y < xs, y < x] ++ [x] ++ qsort [y  y < xs, y >= x]
The qsort
algorithm consists of two function definitions. The first line applies the defined Quicksort to the empty list. The second line represents the general case, where the list consists of at least one element: x:xs
. Here, by convention, x
denotes the beginning of the list and xs
denotes the remainder.
The strategy of the Quicksort algorithm can be implemented almost directly in Haskell:
 use the first element of the list
x
as the pivot element;  insert (
(++)
) all elements inxs
that are lesser thanx
((qsort [y  y < xs, y < x])
) in front of the oneelement list[x]
;  append all elements in
xs
that are at least as large asx
to the list[x]
((qsort [y  y < xs, y >= x])
).
The recursion ends when Quicksort is applied to the empty list. Admittedly, the compactness of Haskell seems unusual. However, this Quicksort algorithm can be implemented in any programming language that supports list comprehension – which leads to the more mainstream Python programming language in Listing 3.
Listing 3
Quicksort in Python
def qsort(L): if len(L) <= 1: return L return qsort([lt for lt in L[1:] if lt < L[0]]) + L[0:1] + qsort([ge for ge in L[1:] if ge >= L[0]])
The description of the Haskell algorithm can be applied almost verbatim to Python. The subtle difference is that in Python, L[0:1]
acts as the first element and you express the list concatenation in Python with the +
symbol. The algorithm reliably performs its services in Figure 2.
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News

Plasma Desktop 6.1.4 Release Includes Improvements and Bug Fixes
The latest release from the KDE team improves the KWin window and composite managers and plenty of fixes.

Manjaro Team Tests Immutable Version of its ArchBased Distribution
If you're a fan of immutable operating systems, you'll be thrilled to know that the Manjaro team is working on an immutable spin that is now available for testing.

Nitrux 3.6.0 ("ie") Available for Download and Install
The latest version of the systemdfree Linux distribution, Nitrux, has been released for general usage and includes the beta of the upcoming NVIDIA 560 graphics driver.

Vanilla OS 2 Available for Download
The latest iteration of Vanilla OS (Orchid) has been released and uses a hybrid Debian base.

DebianBased eLxr Distribution Announced for Edge Deployments
Open source eLxr offers a seamless operating environment for edgetocloud deployments.

NVIDIA Released Driver for Upcoming NVIDIA 560 GPU for Linux
Not only has NVIDIA released the driver for its upcoming CPU series, it's the first release that defaults to using opensource GPU kernel modules.

OpenMandriva Lx 24.07 Released
If you’re into rolling release Linux distributions, OpenMandriva ROME has a new snapshot with a new kernel.

Kernel 6.10 Available for General Usage
Linus Torvalds has released the 6.10 kernel and it includes significant performance increases for Intel Core hybrid systems and more.

TUXEDO Computers Releases InfinityBook Pro 14 Gen9 Laptop
Sporting either AMD or Intel CPUs, the TUXEDO InfinityBook Pro 14 is an extremely compact, lightweight, sturdy powerhouse.

Google Extends Support for Linux Kernels Used for Android
Because the LTS Linux kernel releases are so important to Android, Google has decided to extend the support period beyond that offered by the kernel development team.