2. 2
Introduction
Fastest known sorting algorithm in practice
Average case: O(N log N) (we don’t prove it)
Worst case: O(N2
)
But, the worst case seldom happens.
Another divide-and-conquer recursive
algorithm, like mergesort
3. 3
Quicksort
Divide step:
Pick any element (pivot) v in S
Partition S – {v} into two disjoint groups
S1 = {x S – {v} | x <= v}
S2 = {x S – {v} | x v}
Conquer step: recursively sort S1 and S2
Combine step: the sorted S1 (by the time
returned from recursion), followed by v,
followed by the sorted S2 (i.e., nothing
extra needs to be done)
v
v
S1 S2
S
To simplify, we may assume that we don’t have repetitive elements,
So to ignore the ‘equality’ case!
8. 8
Pick a pivot
Use the first element as pivot
if the input is random, ok
if the input is presorted (or in reverse order)
all the elements go into S2 (or S1)
this happens consistently throughout the recursive calls
Results in O(n2
) behavior (Analyze this case later)
Choose the pivot randomly
generally safe
random number generation can be expensive
9. 9
In-place Partition
If use additional array (not in-place) like MergeSort
Straightforward to code like MergeSort (write it down!)
Inefficient!
Many ways to implement
Even the slightest deviations may cause
surprisingly bad results.
Not stable as it does not preserve the ordering of the
identical keys.
Hard to write correctly
10. 10
int partition(a, left, right, pivotIndex) {
pivotValue = a[pivotIndex];
swap(a[pivotIndex], a[right]); // Move pivot to end
// move all smaller (than pivotValue) to the begining
storeIndex = left;
for (i from left to right) {
if a[i] < pivotValue
swap(a[storeIndex], a[i]);
storeIndex = storeIndex + 1 ;
}
swap(a[right], a[storeIndex]); // Move pivot to its final place
return storeIndex;
} Look at Wikipedia
An easy version of in-place partition to understand,
but not the original form
11. 11
quicksort(a,left,right) {
if (right>left) {
pivotIndex = left;
select a pivot value a[pivotIndex];
pivotNewIndex=partition(a,left,right,pivotIndex);
quicksort(a,left,pivotNewIndex-1);
quicksort(a,pivotNewIndex+1,right);
}
}
12. 12
A better partition
Want to partition an array A[left .. right]
First, get the pivot element out of the way by swapping it with the
last element. (Swap pivot and A[right])
Let i start at the first element and j start at the next-to-last
element (i = left, j = right – 1)
pivot i j
5 6 4 6 3 12 19 5 6 4 6
3 12
19
swap
13. 13
Want to have
A[x] <= pivot, for x < i
A[x] >= pivot, for x > j
When i < j
Move i right, skipping over elements smaller than the pivot
Move j left, skipping over elements greater than the pivot
When both i and j have stopped
A[i] >= pivot
A[j] <= pivot
i j
5 6 4 6
3 12
19
i j
5 6 4 6
3 12
19
i j
<= pivot >= pivot
14. 14
When i and j have stopped and i is to the left of j
Swap A[i] and A[j]
The large element is pushed to the right and the small element is
pushed to the left
After swapping
A[i] <= pivot
A[j] >= pivot
Repeat the process until i and j cross
swap
i j
5 6 4 6
3 12
19
i j
5 3 4 6
6 12
19
15. 15
When i and j have crossed
Swap A[i] and pivot
Result:
A[x] <= pivot, for x < i
A[x] >= pivot, for x > i
i j
5 3 4 6
6 12
19
i
j
5 3 4 6
6 12
19
i
j
5 3 4 6 6 12 19
16. 16
void quickSort(int array[], int start, int end)
{
int i = start; // index of left-to-right scan
int k = end; // index of right-to-left scan
if (end - start >= 1) // check that there are at least two elements to sort
{
int pivot = array[start]; // set the pivot as the first element in the partition
while (k > i) // while the scan indices from left and right have not met,
{
while (array[i] <= pivot && i <= end && k > i) // from the left, look f
i++; // element greater than
while (array[k] > pivot && k >= start && k >= i) // from the right, look f
k--; // element not greater t
if (k > i) // if the left seekindex
swap(array, i, k); // the right index,
// swap the correspondin
}
swap(array, start, k); // after the indices hav
// swap the last element
// the left partition wi
quickSort(array, start, k - 1); // quicksort the left pa
quickSort(array, k + 1, end); // quicksort the right p
}
else // if there is only one element in the partition, do not do any sorting
{
return; // the array is sorted, so exit
Adapted from http://www.mycsresource.net/articles/programming/sorting_algos
Implementation (put the pivot on the leftmost instead of rightmost)
17. 17
void quickSort(int array[])
// pre: array is full, all elements are non-null integers
// post: the array is sorted in ascending order
{
quickSort(array, 0, array.length - 1); // quicksort all the elements in the arr
}
void quickSort(int array[], int start, int end)
{
…
}
void swap(int array[], int index1, int index2) {…}
// pre: array is full and index1, index2 < array.length
// post: the values at indices 1 and 2 have been swapped
18. 18
Partitioning so far defined is ambiguous for
duplicate elements (the equality is included for
both sets)
Its ‘randomness’ makes a ‘balanced’ distribution of
duplicate elements
When all elements are identical:
both i and j stop many swaps
but cross in the middle, partition is balanced (so it’s n log
n)
With duplicate elements …
19. 19
Use the median of the array
Partitioning always cuts the array into roughly half
An optimal quicksort (O(N log N))
However, hard to find the exact median (chicken-
egg?)
e.g., sort an array to pick the value in the middle
Approximation to the exact median: …
A better Pivot
20. 20
Median of three
We will use median of three
Compare just three elements: the leftmost, rightmost and center
Swap these elements if necessary so that
A[left] = Smallest
A[right] = Largest
A[center] = Median of three
Pick A[center] as the pivot
Swap A[center] and A[right – 1] so that pivot is at second last position
(why?)
median3
21. 21
pivot
5 6 4
6
3 12 19
2 13 6
5 6 4 3 12 19
2 6 13
A[left] = 2, A[center] = 13,
A[right] = 6
Swap A[center] and A[right]
5 6 4 3 12 19
2 13
pivot
6
5 6 4 3 12
19
2 13
Choose A[center] as pivot
Swap pivot and A[right – 1]
Note we only need to partition A[left + 1, …, right – 2].
Why?
22. 22
Works only if pivot is picked as
median-of-three.
A[left] <= pivot and A[right] >= pivot
Thus, only need to partition A[left +
1, …, right – 2]
j will not run past the beginning
because a[left] <= pivot
i will not run past the end
because a[right-1] = pivot
The coding style is efficient, but hard to read
24. 24
Small arrays
For very small arrays, quicksort does not
perform as well as insertion sort
how small depends on many factors, such as the
time spent making a recursive call, the compiler,
etc
Do not use quicksort recursively for small
arrays
Instead, use a sorting algorithm that is efficient for
small arrays, such as insertion sort
26. 26
Quicksort Analysis
Assumptions:
A random pivot (no median-of-three partitioning)
No cutoff for small arrays
Running time
pivot selection: constant time, i.e. O(1)
partitioning: linear time, i.e. O(N)
running time of the two recursive calls
T(N)=T(i)+T(N-i-1)+cN where c is a constant
i: number of elements in S1
27. 27
Worst-Case Analysis
What will be the worst case?
The pivot is the smallest element, all the time
Partition is always unbalanced
28. 28
Best-case Analysis
What will be the best case?
Partition is perfectly balanced.
Pivot is always in the middle (median of the array)
29. 29
Average-Case Analysis
Assume
Each of the sizes for S1 is equally likely
This assumption is valid for our pivoting
(median-of-three) strategy
On average, the running time is O(N log N)
(covered in comp271)
30. 30
Quicksort is ‘faster’ than Mergesort
Both quicksort and mergesort take O(N log N) in the
average case.
Why is quicksort faster than mergesort?
The inner loop consists of an increment/decrement (by 1,
which is fast), a test and a jump.
There is no extra juggling as in mergesort.
inner loop
Editor's Notes
#18: If neither i nor j stops, no swaps will be performed (needs code). But the pivot is at the end, unbalanced partition
Leads to n^2.