快速排序算法 & 查找第k大的数
快速排序算法
部分排序与快速排序算法
快速排序含有“二分”的思想,通过递归调用部分排序,大幅度减少比较次数,使得元素之间不需要两两进行比较,但也因此无法保证稳定性。
快速排序算法希望能够选取元素集合中的一个值(一般是首元素)作为基准值,并将它移到一个合适的位置,使得集合中比基准值小的元素都位于基准值左侧,而比不小于基准值的元素都位于基准值的右侧。(将等于基准值的元素划分于基准值的左侧或是右侧都是可以的)
这样,原本无序的集合就变成了 相对有序的两个部分 (但此时这两个部分内部仍然是无序的)。接着就是对每个部分再进行排序,这就刚刚的做法一样了,选取基准值,整理集合,使其成为相对有序的两个部分。
这是一个不断二分的过程,当相对有序的两个部分都只含有一个元素时,即完成二分,就代表某一段已经完全有序。当所有二分工作都完成时,就得到了完整的有序集合。
// 部分排序算法
int partition_sort(vector<int>& nums, int i, int j) {
int key = nums[i];
while (i < j) {
// 从右到左找到第一个不大于key的值
while (i < j && nums[j] > key) j--;
nums[i] = nums[j];
// 从左到右找到第一个大于key的值
while (i < j && nums[i] <= key) i++;
nums[j] = nums[i];
}
nums[i] = key;
return i; // 返回key的位置
}
// 快速排序算法
void quick_sort(vector<int>& nums, int beg, int end) {
if (beg < end) {
int pivot = partition_sort(nums, beg, end);
quick_sort(nums, beg, pivot - 1);
quick_sort(nums, pivot + 1, end);
}
}
查找第k大的数
因为我们实际要找的是第k大的数,那么就只需要保证 比倒数第k个位置上的元素值大的元素都位于此位置的右侧 (这实际上就是基准值的定义)即可。如果将先将集合整体排好序,再选出第k大的数,就会存在不必要的排序过程。
当我们利用部分排序完成对集合的“二分”时,我们可以通过下标来判断第k大的数会落在哪里。如果第k大的元素刚好落在基准值上,那么基准值值的元素值就是我们要找的第k大的元素。如果落在基准值左侧,那么下一步就对左侧的部分进行部分排序。如果落于右侧,则对右侧的部分进行部分排序。
根据我们的目标 【比倒数第k个位置上的元素值大的元素都位于此位置的右侧】,我们希望第k大的数刚好就是基准值。所以,当第k大的数落在基准值上时,我们就找到了答案。
// 该函数需要调用部分排序算法
int quick_select(vector<int>& nums, int begin, int end, int k) {
if (k > nums.size()) return -1;
int pivot;
while (true) {
pivot = partition_sort(nums, begin, end);
if (nums.size() - pivot == k) return nums[pivot];
else if (nums.size() - pivot < k) end = pivot - 1; // 要找的数在 pivot 左边
else begin = pivot + 1; // 要找的数在 pivot 右面
}
}
LeetCode链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array