Quick Sort vs Merge Sort
1. Overview
Quick Sort
- Recursive call happens after partitioning
- The partitioning pivot depends on the data of array
- Not stable
Merge Sort
- Recursive call happens before merging
- The array is divided into two halves of equal length
- Stable
2. Details of Quick Sort
Best Case
- The pivot always lands on the median of an array
Worst Cases
- Bad Order: the original array is sorted
- Bad element: all the element of the original array is duplicated
If the pivot always lands on the median of an array, the calling tree of quick sort will look like a balanced binary tree, so the time complexity is O(nlogn).
In the worst cases, the calling tree will look like a linked list, so the time complexity is O(n^2).
public void QuickSort(int[] a) {
shuffle(a);
sort(a, 0, a.length - 1);
}
private void sort(int[] a, int lo, int hi) {
if (hi <= lo) return;
int j = partition(a, lo, hi);
sort(a, lo, j - 1);
sort(a, j + 1, hi);
}
private int partition(int[] a, int lo, int hi) {
int i = lo; // init the left pointer
int j = hi + 1; // init the right pointer
int v = a[lo]; // init the pivot
while (true) {
while (i < hi && a[++i] < v) {} // scan from left to right,
// until it finds a element larger than pivot
while (lo < j && a[--j] > v) {} // scan from right to left,
// until it finds a element smaller than pivot
if (i >= j) break;
exch(a, i, j); // swap the "unsorted" pair
}
// swap pivot with the rightest element of the left subarray
//
// FROM v <=v >=v
// TO <=v v >=v
exch(a, lo, j);
return j; // return the index of pivot
}
private void exch(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
// Fisher–Yates shuffle algorithm
private void shuffle(int[] a) {
Random random = new Random();
for (int i = a.length - 1; i >= 0; i--) {
// 0 <= r <= i
int r = random.nextInt(i + 1);
exch(a, i, r);
}
}
3. Details of Merge Sort
public void MergeSort(int[] a) {
int[] aux = new int[a.length];
sort(a, 0, a.length - 1);
}
private void sort(int[] a, int lo, int hi) {
if (hi <= lo) return;
int mid = lo + (hi - lo) / 2;
sort(a, lo, mid); // sort the left subarray
sort(a, mid + 1, hi); // sort the right subarray
merge(a, lo, mid, hi); // merge them
}
private void merge(int[] a, int lo, int mid, int hi) {
for (int k = lo; k <= hi; k++) {
aux[k] = a[k];
}
int i = lo; // init the left subarray pointer
int j = mid + 1; // init the right subarray pointer
for (int k = lo; k <= hi; k++) {
if (i > mid) a[k] = aux[j++]; // take a right element (cause the left is used up)
else if (j > hi) a[k] = aux[i++]; // take a left element (cause the right is used up)
else if (aux[j] < aux[i]) a[k] = aux[j++]; // take a right element (cause it's smaller)
else a[k] = aux[i++]; // take a left element (casue it's smaller)
}
}
Note that we use lo + (hi - lo) / 2
instead of (hi + lo) / 2
to avoid integer overflow.
4. Reference
-
Robert Sedgewick and Kevin Wayne’s Algorithm (4th Edition)
-
UC Berkeley CS 61B