Quicksort Algorithm


The quicksort algorithm is a divide and conquer algorithm. Quicksort first divides a large array into two smaller sub-arrays: the low elements and the high elements. Quicksort can then recursively sort the sub-arrays. The steps are:

  1. Pick an element, called a pivot, from the array.
  2. Reorder the array so that all elements with values less than the pivot come before the pivot, while all elements with values greater than the pivot come after it (equal values can go either way). After this reordering, the pivot is in its final, sorted position. This reordering is called the partition operation.
  3. Recursively apply the above steps to the sub-array of elements with smaller values and separately to the sub-array of elements with greater values.

The base case of the recursion is an array of size zero or one, which is in order by definition and requires no further sorting.

The pivot selection and partitioning steps can be done in several different ways; the choice of specific implementation schemes greatly affects the algorithm's performance.

Pseudocode

procedure quicksort(array : list of sortable items, n : length of list)
    quicksort(array, 0, n - 1)
end procedure

procedure quicksort(array : list of sortable items, start : first element of list,
end : last element of list)
    if start < end
        pivot_point ← partition(array, start, end)
        quick_sort(array, start, pivot_point - 1)
        quick_sort(array, pivot_point + 1, end)
    end if
end procedure

procedure partition(array : list of sortable items, start : first element of list,
end : last element of list)
    mid ← (start + end) / 2
    swap array[start] and array[mid]

    pivot_index ← start
    pivot_value ← array[start]
    
    scan ← start + 1
    while scan <= end
        if array[scan] < pivot_value
            pivot_index ← pivot_index + 1
            swap array[pivot_index] and array[scan]
        end if
        scan ← scan + 1
    end while

    swap array[start] and array[pivot_index]

    return pivot_index
end procedure

Example

In this example, the quicksort partition() function is called with the arguments array (the array to partition), 0 (the subscript of the first element of the array), and 11 (the subscript of the last element of the array).

Begin partion

mid ← (start + end) / 2     // (0 + 11) / 2 = 5

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 31 81 22 48 13 67 93 14 45 58 79 72
 

Swap first and middle elements

swap array[start] and array[mid]

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 81 22 48 13 31 93 14 45 58 79 72
 

Compute pivot_index, pivot_value, and scan

pivot_index ← start
pivot_value ← array[start]
scan ← start + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 81 22 48 13 31 93 14 45 58 79 72
 

scan <= end, loop entered. array[scan] (81) not < pivot_value (67).

scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 81 22 48 13 31 93 14 45 58 79 72
 

scan <= end, loop continues. array[scan] (22) < pivot_value (67).

pivot_index ← pivot_index + 1
swap array[pivot_index] and array[scan]
scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 22 81 48 13 31 93 14 45 58 79 72
 

scan <= end, loop continues. array[scan] (48) < pivot_value (67).

pivot_index ← pivot_index + 1
swap array[pivot_index] and array[scan]
scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 22 48 81 13 31 93 14 45 58 79 72
 

scan <= end, loop continues. array[scan] (13) < pivot_value (67).

pivot_index ← pivot_index + 1
swap array[pivot_index] and array[scan]
scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 22 48 13 81 31 93 14 45 58 79 72
 

scan <= end, loop continues. array[scan] (31) < pivot_value (67).

pivot_index ← pivot_index + 1
swap array[pivot_index] and array[scan]
scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 22 48 13 31 81 93 14 45 58 79 72
 

scan <= end, loop continues. array[scan] (93) not < pivot_value (67).

scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 22 48 13 31 81 93 14 45 58 79 72
 

scan <= end, loop continues. array[scan] (14) < pivot_value (67).

pivot_index ← pivot_index + 1
swap array[pivot_index] and array[scan]
scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 22 48 13 31 14 93 81 45 58 79 72
 

scan <= end, loop continues. array[scan] (45) < pivot_value (67).

pivot_index ← pivot_index + 1
swap array[pivot_index] and array[scan]
scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 22 48 13 31 14 45 81 93 58 79 72
 

scan <= end, loop continues. array[scan] (58) < pivot_value (67).

pivot_index ← pivot_index + 1
swap array[pivot_index] and array[scan]
scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 22 48 13 31 14 45 58 93 81 79 72
 

scan <= end, loop continues. array[scan] (79) not < pivot_value (67).

scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 22 48 13 31 14 45 58 93 81 79 72
 

scan <= end, loop continues. array[scan] (72) not < pivot_value (67).

scan ← scan + 1

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 67 22 48 13 31 14 45 58 93 81 79 72
 

scan > end, loop ends.

swap array[start] and array[pivot_index]

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 58 22 48 13 31 14 45 67 93 81 79 72
 

Array is now partitioned.

return pivot_index

  [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
array 58 22 48 13 31 14 45 67 93 81 79 72
 

Complexity

Time Complexity: O(n log n)

Space Complexity: O(log n)

The worst case time complexity for quicksort is O(n2) and the worst case space complexity is O(n). If the pivot happens to be the smallest or largest element in the list (or in some implementations, if all of the elements are equal) one of the sublists produced by the partitioning process may end up as size n - 1.

If that happens repeatedly in every partition, then each recursive call processes a list of size one less than the previous list. That will require n - 1 nested calls before we reach the base case of a list with size 1 and the recursive call tree will be a linear chain of n - 1 nested calls.