LeetCode 215. 数组中的第K个最大元素(练习如何建堆并进行堆排序,大顶堆 & 小顶堆)

本文详细介绍了堆排序算法,包括大顶堆和小顶堆的构建与应用。通过实例解析了如何不使用C++ STL的优先队列来手动建立堆并进行排序。堆排序的时间复杂度为O(nlogn),适合处理大规模数据。此外,还探讨了C++STL中优先队列的使用,提供了多种自定义排序方式。

一、题目:

在这里插入图片描述


二、 解题思路:

这道题很容易,调用内置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

  1. 先拿数组的前k个数建小堆
  2. 之后的数据依次和堆顶比较,如果大于就删除堆顶数据然后插入这个数,继续维护k个数的小堆。
  3. 最后堆中就是数组前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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0rta1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值