一、题目:

二、 解题思路:
这道题很容易,调用内置sort函数,或者自写10大排序算法中其中一种算法就能解决这道题。这里主要练习在不使用 C++ STL 模板 优先队列–> priority_queue (已排序的大小顶堆)的情况下,纯手工建立大小顶堆,然后进行堆排序做这道题。
在此之前极力建议看下这位博主写的文章 👉 什么是大小顶堆? ,再结合我写的实现代码,就很好理解了

堆排序
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它是不稳定排序。 (一般升序采用大顶堆,降序采用小顶堆)
三、大顶堆的建立–>堆排序解法
将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值,如此反复执行,便能得到一个有序序列了。
实现代码:
class Solution {
public:
// 建立大顶堆
void buildBigHeap(vector<int> &nums, int length) {
// 完全二叉树从下往上调整构建大顶堆, i 初始化为最后一个非叶子节点
for (int i = length/2; i >= 0; --i) {
adjustHeap(nums, i, length);
}
}
// 堆调整 (规律:左子节点 2*i+1, 右节点 2*i+2)
void adjustHeap(vector<int> &nums, int i, int length) {
// 左右子节点, largest保存最后一次被父节点替换的子节点下标
int left = 2*i+1; int right = 2*i+2; int largest = i;
// 比较左子节点
if (left < length && nums[largest] < nums[left]) largest = left;
// 比较右子节点
if (right < length && nums[largest] < nums[right]) largest = right;
// largest != i 说明需要交换,交换完后要检查左右子树是否满足大顶堆的性质,不满足则重新调整子树结构
if (largest != i) {
swap(nums[i], nums[largest]);
adjustHeap(nums, largest, length);
}
}
int findKthLargest(vector<int>& nums, int k) {
int len = nums.size();
// 建大顶堆
buildBigHeap(nums, len);
// 我们不必要对堆全排序,排序 k - 1 次,根节点即为第 k 大的数
for (int i = nums.size() - 1; i >= nums.size() - (k-1); --i) {
// 根节点(最大值)与最末尾的叶子节点交换位置
swap(nums[0], nums[i]);
// 排除最后一个叶子节点的情况下,再进行堆调整,这样得出的根节点就是次小值了,以此类推
--len;
adjustHeap(nums, 0, len);
}
return nums[0];
}
};
时间复杂度:O(nlogn)
四、小顶堆的建立–>堆排序解法
明白了大顶堆,小顶堆几乎一个道理,这道题就比较适合使用小顶堆,小根堆最多只需要k个元素在堆里,因此时间复杂度是 O(nlogk), 注:k < n
- 先拿数组的前k个数建小堆
- 之后的数据依次和堆顶比较,如果大于就删除堆顶数据然后插入这个数,继续维护k个数的小堆。
- 最后堆中就是数组前k个最大的值,其中堆顶就是我们要的结果
实现代码:
class Solution {
public:
void buildSmallHeap(vector<int> &nums, int length) {
for (int i = length/2; i >= 0; --i) {
adjustHeap(nums, i, length);
}
}
void adjustHeap(vector<int> &nums, int i, int length) {
int left = 2*i+1; int right = 2*i+2; int minimum = i;
if (left < length && nums[minimum] > nums[left]) minimum = left;
if (right < length && nums[minimum] > nums[right]) minimum = right;
if (minimum != i) {
swap(nums[i], nums[minimum]);
adjustHeap(nums, minimum, length);
}
}
int findKthLargest(vector<int>& nums, int k) {
vector<int> smallHeap(nums.begin(), nums.begin()+k);
int len = smallHeap.size();
buildSmallHeap(smallHeap, len);
// 剩下的元素与堆顶比较,若大于堆顶则值进行交换,并维持小顶堆
for (int i = k; i < nums.size(); ++i) {
if (nums[i] > smallHeap[0]) {
swap(smallHeap[0], nums[i]);
adjustHeap(smallHeap, 0, len);
}
}
return smallHeap[0];
}
};
四、 C++ STL 优先队列模板
升序队列(最小堆)
priority_queue <int,vector<int>,greater<int> > q;
降序队列 (最大堆)
priority_queue <int,vector<int>,less<int> >q;
自定义排序方式:
- 方法一: 重载()
struct cmp{
bool operator()(ListNode* a, ListNode* b){
return a->val > b->val;
}
};
// priority_queue自定义函数的比较与sort正好是相反的
// 也就是说,如果你是把大于号作为第一关键字的比较方式,那么堆顶的元素就是第一关键字最小的
priority_queue<ListNode*, vector<ListNode*>, cmp> pq;
// 此时pq就按照节点的值将最小的放在堆顶
- 方法二: 重载<
struct Status{
int val;
ListNode* node;
bool operator < (const Status &tmp) const{
// 函数必须是静态的 使得该函数可以被 const 对象也就是常量所调用
// 形参可以加上const关键字和&,保证安全性,提高效率
return val > tmp.val;
}
};
priority_queue<Status> pq;
- 方法三: 利用友元函数重载<
struct Status{
int val;
ListNode* node;
// 形参可以加上const关键字和&,保证安全性,提高效率
friend bool operator<(Status a, Status b) {
return a.val > b.val;
}
};
priority_queue<Status> pq;
方法三的另一种写法:
struct node{
int x;
int y;
}point;
bool operator<(const node &a,const node &b) {
return a.x>b.x;
}
priority_queue<node> Q;
本文详细介绍了堆排序算法,包括大顶堆和小顶堆的构建与应用。通过实例解析了如何不使用C++ STL的优先队列来手动建立堆并进行排序。堆排序的时间复杂度为O(nlogn),适合处理大规模数据。此外,还探讨了C++STL中优先队列的使用,提供了多种自定义排序方式。
&spm=1001.2101.3001.5002&articleId=121296942&d=1&t=3&u=1ac5078ebc914282843b9c6422b07ba0)
5805

被折叠的 条评论
为什么被折叠?



