快速排序
快速排序算法的实现思路是:
- 从待排序序列中任选一个元素(假设为 pivot)作为中间元素,将所有比 pivot 小的元素移动到它的左边,所有比 pivot 大的元素移动到它的右边;(这一步被称为「划分 partition」)
- pivot 左右两边的子序列看作是两个待排序序列,各自重复执行第一步。直至所有的子序列都不可再分(仅包含 1 个元素或者不包含任何元素),整个序列就变成了一个有序序列。
void QuickSort(int* a, int left, int right)
{
if (left >= right)//如果区间只剩一个数或没有数就不进行操作
return;
int key = Partition(a, left, right);//调用单趟排序函数,取key的位置
QuickSort(a, left, key - 1);//递归调用,对左区间进行排序
QuickSort(a, key + 1, right);//递归调用,对右区间进行排序
}
「划分 partition」的方法大概有三种,参考:https://blog.csdn.net/LiangXiay/article/details/121421920
记住Hoare方法就好:
right从区间的最右边向左走,找到比key小的元素就停下。left从最左边向右走,找到比key大的元素就停下。然后交换right和left所指向的元素。
重复上面的过程,直到right、left相遇,交换key和right-left相遇位置的元素。
可以看出,经过right、left的不断交换,比key小的值换到了左边,比key大的值换到了右边。最终将key换至中间就完成了单趟排序。
注意:若key取的是最左边的元素,则必须先让right先走。
int Partition(int* a, int left, int right)
{
int key = left;//取最左边的元素做key
while (left < right)//当左右没有相遇
{
while (left < right && a[right] >= a[key])//如果右比key小就退出循环
right--;
while (left < right && a[left] <= a[key])//如果左比key大就退出循环
left++;
swap(a[left], a[right]);//交换左右
}
swap(a[key], a[left]);//交换key和相遇位置的元素
return left;//返回key的位置
}
最坏情况下,快速排序算法的时间复杂度为O(n2)
,理想状态对应的时间复杂度为O(nlogn)
。
实例:
1 #include <iostream>
2 #include <algorithm>
3
4 using namespace std;
5
6 #define ARRAY_SIZE 6
7
8 int Partition(int *a, int left, int right)
9 {
10 int key = left; //取最左边的元素做key
11 while (left < right) //当左右没有相遇
12 {
13 while (left < right && a[right] >= a[key]) //如果右比key小就退出循环
14 right--;
15 while (left < right && a[left] <= a[key]) //如果左比key大就退出循环
16 left++;
17 swap(a[left], a[right]); //交换左右
18 }
19 swap(a[key], a[left]); //交换key和相遇位置的元素
20 return left; //返回key的位置
21 }
22
23 void QuickSort(int *a, int left, int right)
24 {
25 if (left >= right) //如果区间只剩一个数或没有数就不进行操作
26 return;
27 int key = Partition(a, left, right); //调用单趟排序函数,取key的位置
28 QuickSort(a, left, key - 1); //递归调用,对左区间进行排序
29 QuickSort(a, key + 1, right); //递归调用,对右区间进行排序
30 }
31
32 main()
33 {
34 int array[ARRAY_SIZE]{2, 3, 10, 1, 3, 8}; // 数组
35
36 int *p = array;
37 cout << "print original array:" << endl;
38 for (int i = 0; i < ARRAY_SIZE; ++i)
39 {
40 cout << *(p + i) << " ";
41 }
42 cout << endl;
43
44 QuickSort(array, 0, 5);
45 cout << "print sorted array:" << endl;
46 for (int i = 0; i < ARRAY_SIZE; ++i)
47 {
48 cout << *(p + i) << " ";
49 }
50 }
快速选择
寻找数组中第k大的元素,可以用快速选择。
快速选择,其实就是快排中轴值计算的过程。
「划分 partition」 过程是:从子数组 a[l⋯r] 中选择任意一个元素 x 作为主元(pivot),调整子数组的元素使得左边的元素都小于等于它,右边的元素都大于等于它, x 的最终位置就是 q。
所以只要某次划分的 q为倒数第 k 个下标的时候,我们就已经找到了答案。 我们只关心这一点,至于 a[l⋯q−1] 和 a[q+1⋯r] 是否是有序的,我们不关心。
leetcode 215. 数组中的第K个最大元素
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int left = 0;
int right = nums.size() - 1;
int target = nums.size()- k;
while (left < right)
{
int mid = quickSelection(nums, left, right);
if (mid == target)
{
return nums[mid];
}
if (mid < target)
{
left = mid + 1;
} else {
right = mid - 1;
}
}
return nums[left];
}
// 返回Pivot(key)的位置
int quickSelection(vector<int>& a, int left, int right) {
int key = left; //取最左边的元素做key
while (left < right) //当左右没有相遇
{
while (left < right && a[right] >= a[key]) //如果右比key小就退出循环
right--;
while (left < right && a[left] <= a[key]) //如果左比key大就退出循环
left++;
swap(a[left], a[right]); //交换左右
}
swap(a[key], a[left]); //交换key和相遇位置的元素
return left; //返回key的位置
}
};