快速排序
本文将介绍三种快速排序的方法
- 经典快速排序
- 随机快速排序
- 二分快速排序
经典快速排序算法思想
快速排序基于分治的思想,采用递归的方式处理子问题
选取一个哨兵k,这里假设哨兵选取数组第一个元素
即令k = arr[0] = 4
然后设立两个指针 i
和 j
分别初始化指向 第一个元素 和 最后一个元素
第一遍排序
从右往左,依次和哨兵比较,如果大于等于哨兵的值,则 j--
,否则退出循环,然后交换 arr[i]
和 arr[j]
接着从左往右,依次和哨兵比较,如果小于等于哨兵的值,则 i++
, 否者退出循环,然后交换 arr[i]
和 arr[j]
直到 i == j
第一次排序结束,哨兵左侧的值都是小于等于 k
的,右侧都是大于等于 k
的,已经部分有序,接下来进行递归,将左侧继续快速排序,右侧也进行排序
递归结束后,整体就已经有序了
快速排序相比于归并排序来说,不需要另设额外数组,因此空间复杂度会降低,时间复杂度平均情况下也是 O ( N l o g ( N ) ) O(Nlog(N)) O(Nlog(N)),最坏情况下是 O ( N 2 ) O(N^2) O(N2)
以上的解题思路属于书本上的经典快排思想
代码
void quickSort(vector<int>& arr, int left, int right) {
if (left >= right) return;
int i = left;
int j = right;
int k = arr[i];
while (i < j) {
while (i < j && arr[j] >= k) j--;
swap(arr[j], arr[i]);
while (i < j && arr[i] <= k) i++;
swap(arr[i], arr[j]);
}
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
随机快速排序算法思想
经典快速排序中,选择的哨兵是固定的,即选择最左侧的元素
但是,容易存在极端现象,如果哨兵选的不好也容易增大时间复杂度,因此,使用随机快排可以避免这类问题
随机快排就是让哨兵的选取变成随机的,即在 [left, right]
中随机选取其中一个元素作为哨兵,然后和 arr[left]
进行交换,再使用经典排序排序的方法即可
代码
void quickSortByRandom(vector<int>& arr, int left, int right) {
if (left >= right) return;
// 在区间内随机选取一个元素作为哨兵
int k = rand() * (right - left + 1) + left;
// 和left交换
swap(arr[left], arr[k]);
int i = left;
int j = right;
// 快速排序
while(i < j) {
while (i < j && arr[j] >= arr[left]) j--;
while (i < j && arr[i] <= arr[left]) i++;
if (i < j) {
swap(arr[i], arr[j]);
}
}
swap(arr[i], arr[left]);
quickSortByRandom(arr, left, i - 1);
quickSortByRandom(arr, i + 1, right);
}
二分快速排序
利用二分法 + 分治 进行排序
void quickSortByBinary(vector<int>& arr, int left, int right) {
if (left >= right) return;
int i = left;
int j = right;
// 选取中间元素
int mid = arr[i + ((j - i) >> 1)];
do {
while (arr[i] < mid) i++; // 从左到右,找第一个大于等于mid的数
while (arr[j] > mid) j--; // 从右到左,找第一个小于等于mid的数
if (i <= j) { // 范围之内进行交换
swap(arr[i], arr[j]);
i++;
j--;
}
} while(i < j); // 越界条件
quickSortByBinary(arr, left, j); // [left, j] 属于<= mid 的区域
quickSortByBinary(arr, i, right); // [i, right] 属于>= mid 的区域
}