快速排序和快速选择算法
快速排序
快速排序算法的实现思路是:
- 从待排序序列中任选一个元素(假设为 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的位置 } };