最近在看TCPL,第四章的函数与程序结构里面有一个快速排序的例子,并且几句话就把快速排序总结了,非常精炼。快速排序利用的是分治的思想(Divide Conquer),理解了分治,就能理解快排。这里记录一下,并且讲解一下程序的原理。
对于一个给定的数组,从中选择一个元素,以该元素为界将其余元素划分为两个子集,一个子集中的所有元素都小于该元素,另一个子集中的元素都大于或等于该元素。对这两个子集递归执行这一过程当子集中的元素小于2时,这个子集就不需要再次排序,终止递归。
static void sortRecursively(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int pivot = (left + right)/2;
swap(arr, left, pivot);
int last = left;
for (int i = left + 1; i <= right; i++) {
if (arr[left] > arr[i]) {
swap(arr, ++last, i);
}
}
swap(arr, left, last);
sortRecursively(arr, left, last - 1);
sortRecursively(arr, last + 1, right);
}
其中交换数组元素的代码被抽取出来:
static void swap(int[] arr, int k, int j) {
int tmp = arr[k];
arr[k] = arr[j];
arr[j] = tmp;
}
首先选取枢纽点,这里选取的是元素中心位置,然后交换到最左侧的left,这样做的目的便于后面双指针的移动。
交换后并定义last指针指向left位置,这里的last指针表示last之前并且包括last的元素都小于枢纽元素pivot。
之后便开始通过对比枢纽元素与枢纽元素后面的元素,将小于枢纽元素的元素交换到前面,并移动last指针,由于24与67都比23大,所以last就保持不变,也没有任何交换的操作。
当遇到i移动到3的时候,便交换24与3,并且移动last指针指向3。
之后继续移动i,直到最后一个元素。这样,last之前的元素都小于枢纽元素,last之后的元素都大于等于枢纽元素。
然后再恢复划分的子集,让枢纽元素之前的子集和枢纽元素之后的子集分别递归调用排序,递归的终止条件就是子集只有一个元素的时候,这时子集当然是有序的。这样就完成了对整个数组的排序。
对于快速排序排序还有一些复杂的细节,比如枢纽元素的选择等。关于时间复杂度与空间复杂度的分析可以参考维基百科。