LeetCode 之 数组中的第K个最大元素(二)

上一篇文章中,使用冒泡排序和快速排序解题。
本文将使用堆排序解题。
知识点: 优先队列、堆排序、堆的父子结点间关系。
在这里插入图片描述
堆排序有两种实现方法:使用优先队列实现和手写堆排序实现。下面分别介绍并实现这两种方法。

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);
        }
    }


};

参考链接

  1. c++优先队列(priority_queue)用法详解
  2. 堆排序与快速排序解第K个最大元素分析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值