算法的一般思想:
快速排序算法有两个核心点,分别为 “哨兵划分” 和 “递归” 。
哨兵划分操作: 以数组某个元素(一般选取首元素)为 基准数 ,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。
如下图所示,为哨兵划分操作流程。通过一轮 哨兵划分 ,可将数组排序问题拆分为 两个较短数组的排序问题 (本文称之为左(右)子数组)。
递归: 对 左子数组 和 右子数组 递归执行 哨兵划分,直至子数组长度为 1 时终止递归,即可完成对整个数组的排序。
如下图所示,为示例数组 [2,4,1,0,3,5] 的快速排序流程。观察发现,快速排序和 二分法 的原理类似,都是以 log\loglog 时间复杂度实现搜索区间缩小。
我对它的理解:
帮助每个子区间(用递归)的首元素(哨兵)找到它在这个区间(按照某种排序方式后的位置,这里为从小到大)应有的位置,因为此时这个位置 i,它左边所有的元素都比它小,右边的元素都比它大,但左边和右边仍然是无序的,所以需要继续使用递归帮助除了位置 i 的子区间的首元素找到各自应有的位置。
class Solution {
//帮助每个子区间的第一个元素(哨兵)找到它在这个区间应有的位置,因为此时
// 它左边所有的元素都比它小,右边的元素都比它大,那它现在
// 所处的位置就是它按照从小到大后应在的位置
private static void quickSort(int[] arr, int l, int r) {
if (l >= r) return; //这个区间只有一个元素了,根本不需要找它的位置了
int i = l, j = r;
/*然后第一个元素作为标准,就是所谓的
哨兵元素,一般都习惯以第一个元素作为哨兵*/
while (i < j) {
while (i < j && arr[j] >= arr[l]) l--;
while (i < j && arr[i] <= arr[l]) i++;
swap(arr, i, j);
}
/*遇到了不符合“从小到大”的排序规则的元素就进行交换
i,j相遇了,说明已经排好序了*/
swap(arr, i, l);
/*此时,除了哨兵元素之外,因为arr[i]是比arr[l]小的
且i+1一个元素后就比arr[l]大了,所以i需要和l交换,便
可得到哨兵元素应有的位置 i*/
quickSort(arr,l,i-1);
quickSort(arr,i+1,r);
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
复杂度
复杂度分析:
时间复杂度 O(NlogN)O(N \log N)O(NlogN) : 库函数、快排等排序算法的平均时间复杂度为 O(NlogN)O(N \log N)O(NlogN) 。
空间复杂度 O(N)O(N)O(N) : 快速排序的递归深度最好(平均)为 O(logN)O(\log N)O(logN) ,最差情况(即输入数组完全倒序)为 O(N)O(N)O(N)。