堆与堆排序

堆的介绍

参考连接

值得关注的知识点:

  • 堆可以被看做一个一维数组,也可以被看作是棵完全二叉树。堆其实就是利用完全二叉树的结构来维护的一维数组。
  • 堆主要用来做优先队列,因为它可以访问到最重要的元素,而在堆中搜索元素会比较慢。
  • 升序排序用大顶堆,降序排序用小顶堆。以大顶堆为例,没做一次堆排序,就可以得到序列中的最大值,把它放到堆数组的最后面,则堆的末尾就是最大值了,则可以进行升序排序。(因为堆中只能保证最优的元素,不能保证整个堆都是有序的,因此要做根节点和最后一个节点的位置交换)

C++中堆的使用

1、priority_queue

C++中提供优先队列的模板供使用,优先队列的底层实现就是堆,priority_queue包含在头文件#include中。

优先队列priority_queue也是一个队列,含有队列的一切特征,例如先进先出、只能从队头出,队尾入。优先队列在普通队列的基础上增加了内部的一个排序,这个排序本质是由堆实现的。

2、priority_queue的代码原型

template <typename T, typename Container=std::vector<T>, typename Compare=std::less<T>> class priority_queue

可以看出,priority_queue模板有三个参数:

  • 第一个参数为存储对象的类型
  • 第二个参数为存储对象的底层容器,若缺省则默认为vector
  • 第三个参数为函数对象,它定义了一个用来决定元素顺序的断言,若缺省则为大根堆。这里的函数对象可以是标准库中定义好的一些比较仿函数,例如less函数对应获得一个大顶堆,greater对应获得一个小顶堆。

3、priority_queue的初始化

创建空的priority_queue

//默认是一个使用vec作为底层容器的大顶堆
priority_queue<string>words;

使用初始值列表初始化

string words[]{"one", "two", "three"};
priority_queue<string>w{words.begin(), words.end()}; //字母顺序"two", "three", "one"

注:这里跟前面堆介绍中说升序用大根堆,降序用小根堆不一样,有可能原因是C++中优先队列的实现问题。

自定义排序函数时候的初始化

vector<int>values{25,22,54,55};
priority_queue<int>pq{less<int>(),values}; //调用pq的构造函数,其中第一个参数为比较函数对象,第二个函数用于初始化容器元素(可缺省)

4、priority_queue的函数使用

和队列基本相同:

  • top 访问队头元素
  • empty 队列是否为空
  • size 返回队列内元素个数
  • push 插入元素到队尾
  • emplace 原地的构造一个元素并且插入队列
  • pop
  • swap

emplace可以直接调用对象的构造函数,有区别于push的参数传入的形式,属于C++中优化的方式,具体可以参考emplace与push_back

5、priority_queue的使用例子

用pair做优先级队列:先比较第一个元素,后比较第二个元素

	pq.push(pair<int, int>(1, 2));
	pq.push(pair<int, int>(2, 3));
	pq.push(pair<int, int>(2, 4));
	while (!pq.empty()) {
		cout << pq.top().first << " " << pq.top().second << endl;;
		pq.pop();
	}

运行结果:
在这里插入图片描述

经典的topK问题:

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        //优先队列做法(实际上是堆排序)
        
        map<int,int>mp;
        for(auto &_num:nums){
            mp[_num]++;
        }

        auto cmp = [](pair<int,int>a, pair<int, int>b){return a.second > b.second;}; //这里定义为小根堆

        //定义优先队列,按小根堆排序,优先队列中每个pair的第一个元素为数字,第二个元素为出现次数
        priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)>pq{cmp};  //关注这里的定义与初始化

        
        for(auto & [num,count] : mp){
            if(pq.size() == k){
                //大于堆顶元素,则需要把该元素插入堆中,并弹出堆顶元素(因为要维护一个元素数量为k的堆,固要弹出)
                if(pq.top().second < count){ 
                    pq.pop();
                    pq.emplace(num, count);
                }   
            }
            else{
                pq.emplace(num, count);
            }
        }

        vector<int>output;

主要的思路是对元素频次维护一个含有k个元素的小根堆

你可能感兴趣的:(算法,排序算法,算法,数据结构)