快速排序
已知一个数组 nums = [50, 10, 90, 30, 70, 40, 80, 50, 20
,我们需要对这个数组进行排序,因此我们选择了 快速排序 进行排序。
快速排序的思想:选取一个 pivot(哨兵),一般是待排序的数组的第一个元素,将数组分割成两部分,使得前面一部分小于等于 pivot,后面一部分大于等于 pivot;之后再对这两个部分分别进行快速排序。
如下图所示,其中绿色部分小于等于 pivot,红色部分大于等于红色。
快速排序的流程 partition(& nums, low, high):
1、pivot、i 和 j 的定义,pivot=nums[low]; i=low; j=high
;
2、循环条件:i < j
① 从右往左找,nums[j] >= pivot
⇒ j--
;若找到一个比 pivot 小的元素,记录此时不满足条件的下标 j;
② 从左往右找,nums[i] <= pivot
⇒ i++
;若找到一个比 pivot 大的元素,记录此时不满足条件的下标 i;
③ 交换 i 和 j 对应的元素,swap(nums[i], nums[j])
;
④ 直到 i == j
,跳出循环;
3、将交换 nums[i] 与 pivot 进行交换,swap(nums[i], nums[low])
;
4、对绿色部分的进行快速排序,递归调用 partition(nums, low, j-1)
方法;
5、对红色部分的进行快速排序,递归调用 partition(nums, j+1, high)
方法;
i == j
时,i(或者 j)是要与 pivot 进行交换的;并且此时只有 i 的位置是确定的,不会随着后续的快速排序而发生改变,所以可以根据此时的 i 索引得出 nums[i] 是第 k 小(从小到大排序)。- 为什么
i==j
要停止循环?因为当 i 和 j 相遇时,说明已经遍历完所有的元素,此时所有的交换已经完成,此时再继续移动指针已经没有意义。此外,在 i 和 j 相遇时,j 应该指向的是左侧区域最后一个小于 pivot 的元素或者 pivot 自身,i 指向的是右侧第一个大于 pivot 的元素或者自身。- 上述的流程中,必须先
--j
再++i
。因为要保证 pivot 的正确位置,如果先移动 i,nums[i] 会移动到一个等于或大于 pivot 的元素,这时 nums[i] 可能与 pivot 值相等,而后续的 j 往前移动的话,可能没有机会找到小于 pivot 的元素。先减少 j 的索引,可以确保右侧区域的元素都比 pivot 大或等于 pivot,顺利找到小于 pivot 的元素。
假设有个数组[5, 5, 3, 7]
,如果先进行i++
再进行j--
,则 i 首先从 5 不间断地移动到 7,最终i ==j
循环退出,而中间的 3 就不会被正确交换。
class Solution {
public:
void quickSort(vector<int>& nums) {
partition(nums, 0, nums.size()-1);
// 打印数组
}
void partition(vector<int>& nums, int low, int high) {
if (low >= high) return;
int i = low, j = high, pivot = nums[low];
while (i < j) {
while (i < j && nums[j] >= pivot) j--;
while (i < j && nums[i] <= pivot) i++;
if (i < j) swap(nums[i], nums[j]);
}
swap(nums[low], nums[j]);
partition(nums, low, i-1);
partition(nums, i+1, high);
}
};
力扣:数组中的第K个最大元素
215. 数组中的第K个最大元素
根据题意,我们需要对数组进行排序才能知道第
K
K
K 大的元素,因此最简单的解法是整个元素进行排序,然后取第
K
K
K 大(数组从大到小排序)或者第
n
−
K
n-K
n−K 大(数组从小到大排序,其中 n 是数组长度)的元素。
这里我们使用快速排序对数组进行排序,进行快速选择。
每一次排序完,我们都能确定一个元素的索引 idx = i = j
,将这个 idx
与
K
K
K 比较:若一致,则找到这个第
K
K
K 大或者第
n
−
K
n-K
n−K 大的元素;若不一致,则从根据
K
K
K 和 idx 的关系,判断出第
K
K
K 大在[left, idx-1] 范围内还是在[idx+1, right] 范围内,然后另一边的范围我们可以直接忽略掉。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
return partition(nums, nums.size() - k); // 第 n-K 大
}
int partition(vector<int>& nums, int k) {
int le = 0, ri = nums.size() - 1;
while(true) {
int i = le, j = ri;
int idx = rand() % (ri - le + 1) + le;
swap(nums[le], nums[idx]); // 哨兵放在第一位
while (i < j) {
while (i < j && nums[j] >= nums[le]) j--;
while (i < j && nums[i] <= nums[le]) i++;
if (i < j) swap(nums[i], nums[j]);
}
swap(nums[i], nums[le]); // 将相遇点与哨兵元素交换
if (i == k) return nums[i];
else if(i < k) le = i + 1; // 第 k 小的元素在右半部分
else if(i > k) ri = i - 1;
}
}
};