排序——快速排序
1. 思想
快速排序主要运用的是分治的思想。例如对一个典型的子数组 A[p...r]A[p ... r ]A[p...r]进行快速排序,主要分成三步:
(1)分解
数组 A[p...r]A[p ... r ]A[p...r] 被分成两个(可能为空)子数组 A[p...q−1]A[p ... q-1 ]A[p...q−1] 和 A[q+1...r]A[q+1 ... r ]A[q+1...r] ,使得 A[p...q−1]A[p ... q-1 ]A[p...q−1]中的每个元素都小于等于 A[q]A[q]A[q],而 A[q]A[q]A[q] 也小于等于A[q+1...r]A[q+1 ... r ]A[q+1...r] 里的每个元素。其中,计算 q 也是划分过程的一部分;
(2)解决
通过递归调用快速排序,对子数组 A[p...q−1]A[p ... q-1 ]A[p...q−1] 和 A[q+1...r]A[q+1 ... r ]A[q+1...r] 分别进行排序。
(3)合并
因为子数组都是原址排序的,因此不需要合并操作,即此时数组 A[p...r]A[p ... r ]A[p...r]已经是有序的。
2. 代码
利用快速排序算法对数组 a 的所有元素进行排序(从小到大),则开始时需要调用 Quiksort 函数:
void Quiksort(int *a, int p, int r) {
int q;
if(p < r) {
q = Partition(a, p, r);
Quiksort(a, p, q-1);
Quiksort(a, q+1, r);
}
}
该函数的关键在于 Partition,即划分的过程,该函数如下:
int Partition(int *a, int p, int r) {
int x, i, j, temp;
x = a[r];
i = p - 1;
for(j=p; j<r; j++) {
if(a[j] < x) {
temp = a[++i];
a[i] = a[j];
a[j] = temp;
}
}
temp = a[i+1];
a[i+1] = x;
a[r] = temp;
return i+1;
}
partition 函数总是选择一个 x=a[r]x = a[r]x=a[r] 作为主元,并围绕它来划分子数组 A[p...r]A[p ... r]A[p...r]。随着程序的进行,数组被划分成 4 个(可能有空的)区域:
(1)若 $p \leq k \leq i $,则 A[k]≤xA[k] \leq xA[k]≤x;
(2)若 i+1≤k≤j−1i+1 \leq k \leq j-1i+1≤k≤j−1,则 A[k]>xA[k] > xA[k]>x;
(3)若 $k = r $,则 A[k]=xA[k] = xA[k]=x;
(4)若 j≤k≤r−1j \leq k \leq r-1j≤k≤r−1,还未到达。
3. 性能分析
(1)稳定性
快速排序是不稳定的。举个例子,对 [3, 8, 4, 5A, 6, 5B]进行快速排序,最后的结果 5B 会排到 5A 前面。
(2)原地排序
快速排序是原地排序。
(3)时间复杂度
最坏情况下是 O(n2)O(n^2)O(n2);最好情况是 O(nlgn)O(nlgn)O(nlgn)。
(4)空间复杂度
O(lgn)O(lgn)O(lgn)