堆排序以及 TopN 问题
堆排序
堆排序其实是选择排序的优化变种,选择排序是把最大或最小的元素放到最边上,然后不断重复以上过程。 堆排序也是如此,只不过堆排序通过构建数据结构,让查找最大或最小元素并放到最边上的速度比选择排序快得多。
以数组表示完全二叉树
对于一个完全二叉树,我们可以使用数组来进行存储。
使用数组来存储完全二叉树,有如下性质:
- index = 0 的位置存储的是二叉树根节点的值。
- 对于 index 位置的节点,其左孩子的下标为 2*index+1,其右孩子的下标为 2*index+2。
- 对于 childindex,奇数时是左孩子,偶数时是右孩子。其父节点 parentindex = (childindex-1)/2。
堆排序思路
堆——指的是二叉堆,是一种完全二叉树,其父节点的值不小于其孩子节点的值时叫最大堆,父节点不大于其孩子节点的值时叫最小堆。
我们使用数组存储二叉堆,数组的第一个元素也就是data[0],存储了这个数组的最大值,或最小值。如下图所示:
每次将数组中的第一个元素与最后一个元素交换,然后对 [0, len-1),这个区间内的数组值进行调整,循环 n 次后,整个数组就是按照从小到大,或从大到小排好序的了。这个就是堆排序的思路。
TopN 问题
使用堆排序解决 TopN 问题
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
class Solution {
public:
void shiftdown(vector<int>& nums, int root, int len) {
int leftchild = 2*root+1;
int rightchild = 2*root+2;
if (leftchild < len) {
int min_index = leftchild;
if (rightchild < len) {
if (nums[rightchild] < nums[leftchild]) {
min_index = rightchild;
}
}
if (nums[root] > nums[min_index]) {
swap(nums[root], nums[min_index]);
shiftdown(nums, min_index, len);
}
}
}
int findKthLargest(vector<int>& nums, int k) {
// 建小顶堆
int len = nums.size();
for (int i = (len - 1) / 2; i >= 0; --i) {
shiftdown(nums, i, len);
}
// 排序
for (int i = len - 1; i >= 0; --i) {
swap(nums[i], nums[0]);
shiftdown(nums, 0, i);
}
// 取第k个值
return nums[k-1];
}
};
这里我们用了小顶堆,这样就可以得到一个从大到小的排序数组,从而取第 k-1 个元素就是第 k 大的值。这个算法的时间复杂度为 O(nlgn)。
其实我们可以换用其他的排序算法完成同样的计算流程,为什么要采用堆排序呢?
由题目可知,我们只是需要知道第 k 大的元素,并不需要知道整个数组的排序情况,所以可以降低算法的时间复杂度。我们可以修改堆排序的过程,在排序的过程里,使用大顶堆,在执行 k-1 次堆排序后,此时 nums[0] 里存储的就是第 k 大的元素,此时算法的时间复杂度为 O(klgn)。如下代码所示:
class Solution {
public:
void shiftdown(vector<int>& nums, int root, int len) {
int leftchild = 2*root+1;
int rightchild = 2*root+2;
if (leftchild < len) {
int max_index = leftchild;
if (rightchild < len) {
if (nums[rightchild] > nums[leftchild]) {
max_index = rightchild;
}
}
if (nums[root] < nums[max_index]) {
swap(nums[root], nums[max_index]);
shiftdown(nums, max_index, len);
}
}
}
int findKthLargest(vector<int>& nums, int k) {
// 建大顶堆
int len = nums.size();
for (int i = (len - 1) / 2; i >= 0; --i) {
shiftdown(nums, i, len);
}
// 执行 k-1 次
for (int i = len - 1; i >= len - k + 1; --i) {
swap(nums[i], nums[0]);
shiftdown(nums, 0, i);
}
// 此时堆顶就是第 k 大的值
return nums[0];
}
};
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧