
目录
优先级队列以及前面的双端队列基本上已经脱离了队列定义,只是占了队列名字

优先级队列——priority_queue1. 优先级队列是一种容器适配器,根据一些严格的弱排序标准,经过专门设计,其第一个元素始终是它所包含的最大元素。
2. 此上下文类似于堆,其中元素可以随时插入,并且只能检索最大堆元素(优先级队列中顶部的元素)。
3. 优先级队列作为容器适配器实现,容器适配器是使用特定容器类的封装对象作为其基础容器的类,提供一组特定的成员函数来访问其元素。元素从特定容器的“背面”弹出,这称为优先级队列的顶部。
4. 底层容器可以是任何标准容器类模板,也可以是一些其他专门设计的容器类。容器应可通过随机访问迭代器访问,并支持以下操作:
- empty()
- size()
- front()
- push_back()
- pop_back()
5. 标准容器类vector和deque满足这些要求。默认情况下,如果未为特定priority_queue类实例化指定容器类,则使用标准容器vector。
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。这是由容器适配器通过自动调用算法函数 make_heap、push_heap、pop_heap 自动完成的,并在需要时完成。
注意:
- priority_queue还是适配器,但是适配的是vector
- 底层是二叉树的堆
- priority_queue仍然包括在queue头文件中
- 默认大堆
- Compare的缺省是less,表示的是大根堆
可以使用仿函数修改为小根堆
priority_queue<int, deque<int>, greater<int>> pq;


方法一:直接使用sort排序
class Solution { public: int findKthLargest(vector<int>& nums, int k) { sort(nums.begin(), nums.end(), greater<int>()); return nums[k - 1]; } };方法二:优先级队列,建大堆
class Solution { public: int findKthLargest(vector<int>& nums, int k) { priority_queue<int> pq(nums.begin(), nums.end()); while (--k) { pq.pop(); } return pq.top(); } };方法三:维护一个有K个数据的小堆,遍历nums数组,若比top()值大,就入堆,最后返回top()数据
class Solution { public: int findKthLargest(vector<int>& nums, int k) { priority_queue<int, vector<int>, greater<int>> pq(nums.begin(), nums.begin() + k); for (int i = k; i < nums.size(); ++i) { if (nums[i] > pq.top()) { pq.pop(); pq.push(nums[i]); } } return pq.top(); } };
注意:
sort函数最后一个参数是greater
(),是一个匿名对象 ,而在priority_queue<>内部,第三个模板是greater,是类型,不能加括号
我们在之前就遇到过sort函数如果想实现降序,需要加上仿函数greater<类型>(),那么什么是仿函数呢?它又有什么作用?
我们来看一个简单的例子:
- class Fun
- {
- public:
- bool operator()(int a, int b)
- {
- return a < b;
- }
- };
-
- int main()
- {
- Fun func;
- cout << func(1, 2) << endl;
- //等价于
- cout << func.operator()(1, 2) << endl;
-
- return 0;
- }
- 这里的Fun就是仿函数,由Fun类定义的对象称为函数对象
- 仿函数,顾名思义,一个类的对象可以像函数一样使用,它替代了c语言里的函数指针,我们只需要重载 () 符号,就可以像使用函数一样调用它的 () 运算符重载函数
那么这样的仿函数有什么作用呢?
- 替代c语言的函数指针,因为c语言的函数指针很复杂、容易出错
- 搭配模板可以实现多类型的函数运算,不会将函数“写死”,例如:我们写Less、Greater类,重载()符号,在需要的地方实例化函数对象,如果有比较大小的情况就使用函数对象(参数1,参数2),原理就是调用operator()函数,像函数一样调用
下面是priority_queue带上第三个模板参数Less后使用仿函数的代码:
template<class T> class Less { public: bool operator()(const T& a, const T& b) { return a < b; } }; template<class T> class Greater { public: bool operator()(const T& a, const T& b) { return a > b; } }; namespace my_priority_queue { // Less才是Less类的类型 template<class T, class Container = vector, class Compare = Less > class priority_queue { private: //大堆,向下调整 void AdjustDown(int parent) { //创建函数对象,Less模板类型就是<比较,Greater模板类型就是>比较 Compare com; //找左右孩子中最大的哪一个 int child = parent * 2 + 1; while (child < _con.size()) { //因为com是比较小于关系,所以需要将原先大于的表达式逆一下 //判断右孩子是否存在 /*if (child + 1 < _con.size() && _con[child + 1] > _con[child])*/ if (child + 1 < _con.size() && com(_con[child], _con[child + 1])) { ++child; } //if (_con[child] > _con[parent]) if (com(_con[parent], _con[child])) { swap(_con[parent], _con[child]); parent = child; child = parent * 2 + 1; } else { break; } } } void AdjustUp(int child) { Compare com; int parent = (child - 1) / 2; while (child > 0) //最坏情况:孩子等于0时结束 { if (com(_con[parent], _con[child])) { swap(_con[parent], _con[child]); child = parent; parent = child - 1 >> 1; } else { break; } } } public: template<class InputIterator> //input只写迭代器 //迭代器区间构造函数 priority_queue(InputIterator first, InputIterator last) { while (first != last) { _con.push_back(*first); ++first; } //建堆 for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i ) { AdjustDown(i); } } void pop() { //交换后,尾删,并向下调整 swap(_con[0], _con[_con.size() - 1]); _con.pop_back(); AdjustDown(0); } void push(const T& x) { _con.push_back(x); AdjustUp(_con.size() - 1); } private: //容器 Container _con; }; }如果比较的类型是其他自定义类型,并且该类没有重载operator<函数,那么我们就需要手写一个仿函数进行比较,否则编译错误,无法进行比较
因为库里的仿函数使用了模板,是什么类型就按什么类型相比,那么如果是new返回的指针类型,由于每次new返回的地址相当于是随机的,又比较的是指针类型的大小,所以比较结果也是随机结果。所以我们还是需要写仿函数,修改比较方式,去比较指针指向的内容即可。
编译错误,找不到出错位置怎么办?
如果代码编译错误,找不到在哪,那么逐步屏蔽掉一些代码,逐步排查,是十分有效的找到错误处方法
priority_queue也同queue、stack一样,是适配器,适配vector容器
- 采用封装容器vector的push_back尾插数据,因为默认是大堆,向下建堆,所以尾插数据后,将从最后一个元素的父亲结点—— (_con.size() - 1 - 1) / 2 开始向下调整。
- 向下调整函数不用多说,在二叉树部分我们详细讲解过
- namespace my_priority_queue
- {
- // Less
才是Less类的类型 - template<class T, class Container = vector
, class Compare = Less> - class priority_queue
- {
- private:
- //大堆,向下调整
- void AdjustDown(int parent)
- {
- //创建函数对象,Less模板类型就是<比较,Greater模板类型就是>比较
- Compare com;
-
- //找左右孩子中最大的哪一个
- int child = parent * 2 + 1;
- while (child < _con.size())
- {
- //因为com是比较小于关系,所以需要将原先大于的表达式逆一下
-
- //判断右孩子是否存在
- /*if (child + 1 < _con.size() && _con[child + 1] > _con[child])*/
- if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
- {
- ++child;
- }
-
- //if (_con[child] > _con[parent])
- if (com(_con[parent], _con[child]))
- {
- swap(_con[parent], _con[child]);
- parent = child;
- child = parent * 2 + 1;
- }
- else
- {
- break;
- }
- }
-
- }
-
- public:
- template<class InputIterator> //input只写迭代器
-
- //迭代器区间构造函数
- priority_queue(InputIterator first, InputIterator last)
- {
- while (first != last)
- {
- _con.push_back(*first);
- ++first;
- }
-
- //建堆
- for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i )
- {
- AdjustDown(i);
- }
- }
-
-
- private:
- //容器
- Container _con;
- };
- }
- 堆的pop,将堆顶数据与最后一个数据进行交换,再进行pop_back,再将堆顶数据向下调整
- void pop()
- {
- //交换后,尾删,并向下调整
- swap(_con[0], _con[_con.size() - 1]);
- _con.pop_back();
-
- AdjustDown(0);
- }
- 尾插数据后,将该数据向上调整
- void AdjustUp(int child)
- {
- Compare com;
-
- int parent = (child - 1) / 2;
- while (child > 0) //最坏情况:孩子等于0时结束
- {
- if (com(_con[parent], _con[child]))
- {
- swap(_con[parent], _con[child]);
- child = parent;
- parent = child - 1 >> 1;
- }
- else
- {
- break;
- }
- }
- }
-
- void push(const T& x)
- {
- _con.push_back(x);
- AdjustUp(_con.size() - 1);
- }
- 返回堆顶数据,返回值是 const T&
- const T& top()
- {
- return _con[0];
- }
- 返回vector的size()即可
- size_t size()
- {
- return _con.size();
- }
- 直接调用vector的empty函数即可
- size_t size()
- {
- return _con.size();
- }
- #pragma once
- #include
- #include
- #include
- using namespace std;
-
- template<class T>
- class Less
- {
- public:
- bool operator()(const T& a, const T& b)
- {
- return a < b;
- }
- };
-
- template<class T>
- class Greater
- {
- public:
- bool operator()(const T& a, const T& b)
- {
- return a > b;
- }
- };
-
- namespace my_priority_queue
- {
- // Less
才是Less类的类型 - template<class T, class Container = vector
, class Compare = Less> - class priority_queue
- {
- private:
- //大堆,向下调整
- void AdjustDown(int parent)
- {
- //创建函数对象,Less模板类型就是<比较,Greater模板类型就是>比较
- Compare com;
-
- //找左右孩子中最大的哪一个
- int child = parent * 2 + 1;
- while (child < _con.size())
- {
- //因为com是比较小于关系,所以需要将原先大于的表达式逆一下
-
- //判断右孩子是否存在
- /*if (child + 1 < _con.size() && _con[child + 1] > _con[child])*/
- if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
- {
- ++child;
- }
-
- //if (_con[child] > _con[parent])
- if (com(_con[parent], _con[child]))
- {
- swap(_con[parent], _con[child]);
- parent = child;
- child = parent * 2 + 1;
- }
- else
- {
- break;
- }
- }
-
- }
-
- void AdjustUp(int child)
- {
- Compare com;
-
- int parent = (child - 1) / 2;
- while (child > 0) //最坏情况:孩子等于0时结束
- {
- if (com(_con[parent], _con[child]))
- {
- swap(_con[parent], _con[child]);
- child = parent;
- parent = child - 1 >> 1;
- }
- else
- {
- break;
- }
- }
- }
-
- public:
-
- priority_queue()
- {}
-
- //迭代器区间构造函数
- template<class InputIterator> //input只写迭代器
- priority_queue(InputIterator first, InputIterator last)
- {
- while (first != last)
- {
- _con.push_back(*first);
- ++first;
- }
-
- //建堆
- for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i )
- {
- AdjustDown(i);
- }
- }
-
- void pop()
- {
- //交换后,尾删,并向下调整
- swap(_con[0], _con[_con.size() - 1]);
- _con.pop_back();
-
- AdjustDown(0);
- }
-
- void push(const T& x)
- {
- _con.push_back(x);
-
- AdjustUp(_con.size() - 1);
- }
-
- const T& top()
- {
- return _con[0];
- }
-
- size_t size()
- {
- return _con.size();
- }
-
- bool empty()
- {
- return _con.empty();
- }
-
- private:
- //容器
- Container _con;
- };
- }
-
- void test_priority_queue1()
- {
- // 默认是大堆 -- less
- //priority_queue
pq; -
- // 仿函数控制实现小堆
- my_priority_queue::priority_queue<int, vector<int>, Greater<int>> pq;
-
- pq.push(3);
- pq.push(5);
- pq.push(1);
- pq.push(4);
-
- while (!pq.empty())
- {
- cout << pq.top() << " ";
- pq.pop();
- }
- cout << endl;
- }
priority_queue优先级队列就是存储在vector内的堆,掌握向上向下调整函数,可以回顾之前的文章堆的实现一节。
最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!
