在各种排序算法中,面试最常考应该就是快排和堆排序了。今天我们就来实现以下。
快速排序
快排的核心思想就是从一个数组中挑选出一个中间值,并把数组分为两个部分,左边的数据都小于中间值,右边的数据都大于中间值。经过不断地递归挑选、分割数组,就可以完成数组的排序。
class Solution {
public:
/*
* 1. 必须左右都是闭区间,因为两边都会被用到
* 2. 虽然采用闭区间,但循环结束条件必须是小于
* 3. 移动值的时候需要囊括等于的情况
* 4. 别忘记递归结束条件
*/
int partition(vector<int>& nums, int left, int right) {
int idx = rand() % (right - left + 1) + left;
int pivot = nums[idx];
swap(nums[left], nums[idx]);
while(left < right) {
while(left < right && nums[right] >= pivot) --right;
nums[left] = nums[right];
while(left < right && nums[left] <= pivot) ++left;
nums[right] = nums[left];
}
nums[left] = pivot;
return left;
}
void quickSort(vector<int>& nums, int left, int right) {
if(left > right) return;
int mid = partition(nums, left, right);
quickSort(nums, left, mid - 1);
quickSort(nums, mid + 1, right);
}
vector<int> sortArray(vector<int>& nums) {
quickSort(nums, 0, nums.size() - 1);
return nums;
}
};
本代码通过了leetcode 912.排序数组的所有测试用例。需要注意的是,快速排序挑选pivot的时候最好是随机选取,否则面对基本有序的数组会退化成O(n)的算法。如果数组中有大量的重复用例,可以考虑把数组分割成三个部分[小于中间值的, 所有等于中间值的, 大于中间值的]减少递归层数。
总结:快排是一种不稳定的算法,平均时间复杂度为O(n)。
快排应用--无序数组的中位数
有序数组的中位数求取很简单,直接找到数组中间的数字或者是中间两个数字的平均数。那么如何求解无序数组的中位数呢?核心思想其实还是对数组进行排序,但我们可以尽量减少排序的部分。
对于快排来说,每次选取的中间数的位置就是它最后的位置,这点需要注意到。我们可以利用这点,每次对partition的返回值mid进行判断。如果mid < index,说明分割的数据位于中间位置的左边,我们只需对右边进行排序即可。
class Solution {
public:
int partition(vector<int>& nums, int left, int right) {
int pivot = nums[left];
while(left < right) {
while(left < right && nums[right] >= pivot) --right;
nums[left] = nums[right];
while(left < right && nums[left] <= pivot) ++left;
nums[right] = nums[left];
}
nums[left] = pivot;
return left;
}
//index就是我们关心的位置,求中位数的话,就是vec.size()/2,偶数位数组还需要-1
void quickSort(vector<int>& nums, int left, int right, int index) {
if(left > right) return;
int mid = partition(nums, left, right);
//只排序需要排序的部分
if(mid <= index) {
quickSort(nums, mid + 1, right, index);
}
else {
quickSort(nums, left, mid - 1, index);
}
}
};
需要注意的是:如果数组的长度为偶数,例如[2, 4, 2, 1],index的值需要中间值-1,即vec.size()/2 - 1,这样才能确保中间两个位置的数字都是排序过后的。
快排应用--无序数组的第k大/小的数字
既然我们可以求取中位数,当然可以求取任意位置的数字。只要修改我们需要求取的index即可,估计还需要把上面的代码做一定修改。并且堆排序可能还是更适合一些,故不做展开阐述。
堆排序
堆排序常用来解决第K大/小的数字问题。与快排是每次找到数组中的一个数字序号不同。大顶堆/小顶堆每次都是找到数组中的极大值/极小值。
需要注意的是:自底向上建堆的时间复杂度是O(n),整个堆排序的时间复杂度也是O(logN)。实现如下:
class Solution {
public:
void adjustdown(vector<int>& nums, int size, int index) {
int left = index * 2 + 1;
int right = index * 2 + 2;
int maxIdx = index;
if(left < size && nums[left] > nums[maxIdx]) maxIdx = left;
if(right < size && nums[right] > nums[maxIdx]) maxIdx = right;
if(index != maxIdx) {
swap(nums[index], nums[maxIdx]);
adjustdown(nums, size, maxIdx);
}
}
void headSort(vector<int>& nums, int size, int k) {
//建大顶堆
for(int i = size/2 - 1; i >= 0; --i) {
adjustdown(nums, size, i);
}
//排序
for(int i = size - 1; i >= size - k; --i) {
swap(nums[0], nums[i]);
adjustdown(nums, i, 0);
}
}
int findKthLargest(vector<int>& nums, int k) {
headSort(nums, nums.size(), k);
return nums[nums.size() - k];
}
};