排序算法 —— 快速排序(quick sort)
推荐可视化网页:visualgo, sorting.at, usfca, youtube.
快速排序算法分三步:
- 取pivot,并将数组分成大于pivot和小于pivot两部分,并且小于pivot的部分在pivot左边,大于的在右边。
- 对每边分别进行快速排序(递归)。
- 将排序好的部分合起来。
Lomuto partition scheme
每次取最左边一个数作为pivot。
public void quickSort(int[] nums) {
quickSortHelper(nums, 0, nums.length - 1);
}
private void quickSortHelper(int[] nums, int head, int tail) {
if (head < tail) {
int threshold = findThreshold(nums, head, tail);
quickSortHelper(nums, head, threshold - 1);
quickSortHelper(nums, threshold + 1, tail);
}
}
private int findThreshold(int[] nums, int head, int tail) {
int pivot = head;
int index = head + 1;
for (int i = index; i <= tail; i++) {
if (nums[i] < nums[pivot]) {
swap(nums, i, index);
index++;
}
}
swap(nums, pivot, index - 1);
return index - 1;
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
实际上findThreshold中的循环可以用两个指针进行:
private int findThreshold(int[] nums, int head, int tail) {
int pivot = head;
int i = head + 1;
int j = tail;
while (i < j) {
while (nums[i] < nums[pivot]) {
i++;
}
while (nums[j] > nums[pivot]) {
j--;
}
if (i < j) swap(nums, i, j);
}
swap(nums, pivot, j);
return j;
}
Hoare partition scheme
取数组中间数作为pivot,用两个指针,一个指向开头,另一个指向结尾,彼此靠近,直到位于数组中的一对元素,一个大于pivot,一个小于pivot,则对这两个元素进行交换。
algorithm quicksort(A, lo, hi) is
if lo < hi then
p := partition(A, lo, hi)
quicksort(A, lo, p)
quicksort(A, p + 1, hi)
algorithm partition(A, lo, hi) is
pivot := A[⌊(hi + lo) / 2⌋]
i := lo - 1
j := hi + 1
loop forever
do
i := i + 1
while A[i] < pivot
do
j := j - 1
while A[j] > pivot
if i ≥ j then
return j
swap A[i] with A[j]
本身的Hoare partition scheme算法不能解决元素重复问题,因此要做改进。
- 对pivot选择:
mid := (lo + hi) / 2
if A[mid] < A[lo]
swap A[lo] with A[mid]
if A[hi] < A[lo]
swap A[lo] with A[hi]
if A[mid] < A[hi]
swap A[mid] with A[hi]
pivot := A[hi]
- 重复问题:
algorithm quicksort(A, lo, hi) is
if lo < hi then
p := pivot(A, lo, hi)
left, right := partition(A, p, lo, hi) // note: multiple return values
quicksort(A, lo, left - 1)
quicksort(A, right + 1, hi)
reference:wiki-quickSort