上一篇文章中,使用冒泡排序和快速排序解题。
本文将使用堆排序解题。
知识点: 优先队列、堆排序、堆的父子结点间关系。
堆排序有两种实现方法:使用优先队列实现和手写堆排序实现。下面分别介绍并实现这两种方法。
1. 优先队列实现堆排序
优先队列 priority_queue 本质上也是一种队列,因此使用时需要导入库 #include<queue>
优先队列与普通队列的不同之处在于:优先队列按照优先级进行排队,优先级高的排在前面,优先出队。优先级可以由我们来定义,如果使用STL模板 priority_queue,默认为大根堆,即由大到小排序。当然我们也可以定义从小到大排序,优先级的定义方法如下:
// 优先级从小到大排序 小根堆
priority_queue<int, vector<int>, greater<int>> minheap;
// 优先级从大到小排序 大根堆
priority_queue<int, vector<int>, less<int>> maxheap;
1.1 小根堆
此处,我们使用小根堆,由小到大排序找到数组中第K个最大元素。
首先将数组变成一个从小到大排序的优先队列,之后让队列的前 nums.size() - k 个元素出队,此时队列的最前端是第K个最大元素,返回该元素即可。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> minheap;
for(int i = 0; i < nums.size(); i++)
{
minheap.push(nums[i]);
}
for(int j = 0; j < nums.size() - k; j++)
{
minheap.pop();
}
return minheap.top();
}
};
1.2 大根堆
此处,我们使用大根堆,由大到小排序找到数组中第K个最大元素。
首先将数组变成一个从大到小排序的优先队列,之后让队列的前 k - 1 个元素出队,此时队列的最前端是第K个最大元素,返回该元素即可。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, less<int>> maxheap;
for(int i = 0; i < nums.size(); i++)
{
maxheap.push(nums[i]);
}
for(int j = 0; j < k - 1; j++)
{
maxheap.pop();
}
return maxheap.top();
}
};
2. 手写堆排序
如果不使用优先队列可以做到堆排序吗?可以
首先进行建堆,实现一个大根堆或者小根堆。因此需要一个堆调整函数 void heapify(vector<int>& nums, int n, int i)
,调整根结点和左右子结点的位置。以大根堆为例,则需要将最大数放在堆顶。调整后的子堆需要继续调整以保证大根堆的定义,举个例子来说明:如果左子结点大于根结点,那么交换二者的值,交换后左子结点位置上的值要小于其左右结点,不满足大根堆的性质,故需要继续进行调整。
那么需要对所有结点都进行调整吗?
仅需对非叶结点进行调整即可,从堆中最后一个非叶结点开始调整。
建堆完成后,将堆顶元素放在数组末尾。并对剩余数组元素继续进行堆调整,直至第 k 大的数在堆顶,返回堆顶元素即可。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
buid_heap(nums);
// 第一次循环后,堆顶为第二大的数,第一大的数在数组末尾,因此第 k 大的数在堆顶时, i = n - (k - 1)
for(int i = n - 1; i >= n - (k - 1); i--)
{
swap(nums[i], nums[0]);
// n - 1 个数参与调整
heapify(nums, i, 0);
}
return nums[0];
}
void heapify(vector<int>& nums, int n, int i)
{
int left = 2 * i + 1;
int right = 2 * i + 2;
int maxmum = i;
if(left < n && nums[maxmum] < nums[left])
maxmum = left;
if(right < n && nums[maxmum] < nums[right])
maxmum = right;
if(i != maxmum)
{
swap(nums[i], nums[maxmum]);
heapify(nums, n, maxmum);
}
}
void buid_heap(vector<int>& nums)
{
int n = nums.size();
// 子结点为 i ,则父结点为 (i - 1) / 2
// 数组最后一个元素下标为 n - 1 , 所以 (i - 1) / 2 = (n - 1 - 1) / 2 = n / 2 - 1
for(int i = n / 2 - 1; i >= 0; i--)
{
heapify(nums, n, i);
}
}
};