👉priority_queue 的介绍👈
优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包的元素中最大的。
此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队中位于顶部的元素)。
优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类.priority_queue 提供一组特定的成员函数来访问其元素,元素从特定容器的尾部弹出,其称为优先队列的顶部。
底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素
pop_back():删除容器尾部元素
标准容器类 vector 和 deque 满足这些需求。默认情况下,如果没有为特定的 priority_queue 类实例化指定容器类,则使用 vector。
需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap 和 pop_heap 来自动完成此操作。
👉priority_queue 的使用👈
优先级队列默认使用 vector 作为其底层存储数据的容器。在 vector 上又使用了向上调整算法和向下调整算法将 vector 中元素构造成堆的结构,因此 priority_queue 就是堆。所有需要用到堆的地方,都可以考虑使用priority_queue。注意:默认情况下 priority_queue 是大堆。
函数声明 | 接口说明 |
priority_queue() | 构造一个空的优先级队列 |
priority_queue(first, last) | 迭代器区间构造优先级队列 |
empty( ) | 检测优先级队列是否为空,是返回 true,否则返回 false |
top( ) | 返回优先级队列中最大元素(最小元素),即堆顶元素 |
push(x) | 在优先级队列中插入元素 x |
pop() | 删除优先级队列中最大元素(最小元素),即堆顶元素 |
size() | 返回优先级队列中元素的个数 |
代码示例:
#include <queue> // 优先级队列priority的头文件 #include <iostream> using namespace std; #include <functional> // greater算法的头文件 int main() { // 默认情况下,创建的是大堆,其底层是按照小于号比较的 priority_queue<int> pq1; pq1.push(3); pq1.push(2); pq1.push(8); pq1.push(5); pq1.push(1); while (!pq1.empty()) { cout << pq1.top() << " "; pq1.pop(); } cout << endl; // 如果要创建小堆,将第三个模板参数换成greater比较方式,其底层是按照大于号比较的 priority_queue<int, vector<int>, greater<int>> pq2; pq2.push(3); pq2.push(2); pq2.push(8); pq2.push(5); pq2.push(1); while (!pq2.empty()) { cout << pq2.top() << " "; pq2.pop(); } cout << endl; return 0; }
如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供 > 或者 < 的重载。
#include <queue> // 优先级队列priority的头文件 #include <functional> // greater算法的头文件 #include <iostream> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} bool operator<(const Date& d)const { return (_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day); } bool operator>(const Date& d)const { return (_year > d._year) || (_year == d._year && _month > d._month) || (_year == d._year && _month == d._month && _day > d._day); } friend ostream& operator<<(ostream& _cout, const Date& d) { _cout << d._year << "-" << d._month << "-" << d._day; return _cout; } private: int _year; int _month; int _day; }; void TestPriorityQueue() { // 大堆,需要用户在自定义类型中提供<的重载 priority_queue<Date> q1; q1.push(Date(2018, 10, 29)); q1.push(Date(2018, 10, 28)); q1.push(Date(2018, 10, 30)); while (!q1.empty()) { cout << q1.top() << " "; q1.pop(); } cout << endl; // 如果要创建小堆,需要用户提供>的重载 priority_queue<Date, vector<Date>, greater<Date>> q2; q2.push(Date(2018, 10, 29)); q2.push(Date(2018, 10, 28)); q2.push(Date(2018, 10, 30)); while (!q2.empty()) { cout << q2.top() << " "; q2.pop(); } cout << endl; } int main() { TestPriorityQueue(); return 0; }
优先级的队列使用起来没有什么难的,接下来我们来做一道题目,顺便回顾一下堆。
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
解决这道题有两种思路,第一种就是用数组的元素建大堆,然后 pop 掉 k - 1 个元素,此时堆顶的元素就是第 k 大的数。这种思路的时间复杂度为O(N + k * lgN),当 N 很大时,就会需要非常多的空间。这时候,我们可以考虑第二种思路就是用前 k 个数建只包含 k 个数的小堆,再遍历数组剩下的元素,比堆顶元素大则替换掉堆顶元素,再向下调整堆。这种思路的时间复杂度为O(k + (N - k) * lgk)注:顶元素不允许修改,因为堆顶元素修改可以会破坏堆的特性。
class Solution { public: int findKthLargest(vector<int>& nums, int k) { // 建n个数的大堆 priority_queue<int> pq(nums.begin(), nums.end()); // pop掉前k-1个大的数 while(--k) pq.pop(); return pq.top(); } };
class Solution { public: int findKthLargest(vector<int>& nums, int k) { // 建k个数的小堆,从下标k开始遍历数组 // 如果num[i]大于堆顶的数据,那么nums[i]入堆 // 遍历结束,堆顶的数就是第k大的数 priority_queue<int, vector<int>, greater<int>> pq(nums.begin(), nums.begin() + k); for(size_t i = k; i < nums.size(); ++i) { if(nums[i] > pq.top()) { pq.pop(); pq.push(nums[i]); } } return pq.top(); } };
👉仿函数👈
仿函数也称为函数对象,它是具有operator()
的类对象,可以想函数一样来使用它。
#include <iostream> using namespace std; // 仿函数/函数对象 template <class T> class Less { public: template <class T> bool operator()(const T& left, const T& right) { return left < right; } }; template <class T> class Greater { public: template <class T> bool operator()(const T& left, const T& right) { return left > right; } }; int main() { // 仿函数用起来就像是函数 // 有名对象 Less<int> lessFunc; cout << lessFunc(1, 2) << endl; // lessFunc(1, 2) <==> lessFunc.operator()(1, 2); // 匿名对象 cout << Greater<int>()(1, 2) << endl; return 0; }
这样一看,好像仿函数也没有特别大的又是,和函数指针也没有什么大的区别嘛!其实不然,函数指针需要写函数的参数和返回值类型。所以仿函数用起来还是比函数指针舒服的。
#include <iostream> using namespace std; // 仿函数/函数对象 template <class T> class Less { public: template <class T> bool operator()(const T& left, const T& right) { return left < right; } }; template <class T> class Greater { public: template <class T> bool operator()(const T& left, const T& right) { return left > right; } }; template <class T, class Compare> void BubbleSort(T* a, int n, Compare com) { for (int i = 0; i < n - 1; ++i) { int exchange = 0; for (int j = 1; j < n - i; ++j) { // if (a[i] < a[i - 1]) if (com(a[j], a[j - 1])) { swap(a[j], a[j - 1]); exchange = 1; } } if (exchange == 0) break; } } int main() { Less<int> lessFunc; int arr[] = { 2, 7,3, 1, 5, 9 ,10,20 }; BubbleSort(arr, sizeof(arr) / sizeof(arr[0]), lessFunc); for (auto e : arr) cout << e << " "; cout << endl; BubbleSort(arr, sizeof(arr) / sizeof(arr[0]), Greater<int>()); for (auto e : arr) cout << e << " "; cout << endl; return 0; }
注:没有成员变量的类的大小是 1 个字节,所以传参时加不加引用都没有关系。如果参数用 const 修饰了,那么仿函数也要用 const 来修饰。
如果库提供的仿函数不符合我们的需求,我们可以自己写!
#include <queue> // 优先级队列priority的头文件 #include <functional> // greater算法的头文件 #include <iostream> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} bool operator<(const Date& d)const { return (_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day); } bool operator>(const Date& d)const { return (_year > d._year) || (_year == d._year && _month > d._month) || (_year == d._year && _month == d._month && _day > d._day); } friend ostream& operator<<(ostream& _cout, const Date& d) { _cout << d._year << "-" << d._month << "-" << d._day; return _cout; } private: int _year; int _month; int _day; }; struct PDateLess { bool operator()(const Date* d1, const Date* d2) { return *d1 < *d2; } }; struct PDateGreater { bool operator()(const Date* d1, const Date* d2) { return *d1 > *d2; } }; void TestPriorityQueue() { priority_queue<Date*, vector<Date*>, PDateLess> q3; q3.push(new Date(2018, 10, 29)); q3.push(new Date(2018, 10, 28)); q3.push(new Date(2018, 10, 30)); while (!q3.empty()) { cout << *q3.top() << " "; q3.pop(); } cout << endl; priority_queue<Date*, vector<Date*>, PDateGreater> q4; q4.push(new Date(2018, 10, 29)); q4.push(new Date(2018, 10, 28)); q4.push(new Date(2018, 10, 30)); while (!q4.empty()) { cout << *q4.top() << " "; q4.pop(); } cout << endl; } int main() { TestPriorityQueue(); return 0; }
现在学习到的仿函数还只是冰山一角,以后还会有更多的仿函数要学!!!
👉priority_queue 的模拟实现👈
// PriorityQueue.h #pragma once namespace Joy { // 仿函数 template <class T> struct less { bool operator()(const T& left, const T& right) { return left < right; } }; template <class T> struct greater { bool operator()(const T& left, const T& right) { return left > right; } }; template <class T, class Container = vector<T>, class Compare = less<T>> class priority_queue { public: priority_queue() : _con() {} template <class InputIterator> priority_queue(InputIterator first, InputIterator last) : _con(first, last) { // 注意:这里要使用int!如果使用size_t,如果只有一个元素会出现Bug int size = _con.size(); for (int i = (size - 2) / 2; i >= 0; --i) adjust_down(i); } void push(const T& val) { _con.push_back(val); adjust_up(_con.size() - 1); } void pop() { //swap(_con[0], _con[_con.size() - 1]); swap(_con.front(), _con.back()); _con.pop_back(); adjust_down(0); } bool empty() const { return _con.empty(); } size_t size() const { return _con.size(); } const T& top() const { assert(!empty()); return _con[0]; } private: Container _con; // 底层容器 // 向上调整算法 void adjust_up(size_t child) { size_t parent = (child - 1) / 2; while (child > 0) { //if(_con[parent] < _con[child]) if (Compare()(_con[parent], _con[child])) // 匿名对象 { swap(_con[parent], _con[child]); child = parent; parent = (child - 1) / 2; } else break; } } void adjust_down(size_t parent) { size_t child = 2 * parent + 1; size_t size = _con.size(); while (child < size) { // 找出较大的孩子 if (child + 1 < size && Compare()(_con[child], _con[child + 1])) ++child; if (Compare()(_con[parent], _con[child])) { swap(_con[parent], _con[child]); parent = child; child = 2 * parent + 1; } else break; } } }; } // Test.cpp #include <vector> #include <iostream> using namespace std; #include "PriorityQueue.h" int main() { //priority_queue<int> pq; 默认是大堆 //Joy::priority_queue<int> pq; Joy::priority_queue<int, vector<int>, greater<int>> pq; pq.push(3); pq.push(2); pq.push(8); pq.push(5); pq.push(1); while (!pq.empty()) { cout << pq.top() << " "; pq.pop(); } cout << endl; return 0; }
优先级队列的实现就不讲解了,和用C语言最大的区别就是不需要自己造轮子了,直接使用 vector 适配出优先级队列。还有就是使用了仿函数,可以根据传入的仿函数生成大堆还是小堆。如果还有相关的实现细节不了解的话,可以看这篇文章:堆的实现!
👉反向迭代器👈
方向迭代器其实也是一种适配器,它可以适配出各种容器的反向迭代器,其主要的是将正向迭代器进行了封装,反向迭代器 ++ 就复用正向迭代器的 - -,反向迭代器 - - 就复用正向迭代器的 ++。
#pragma once template <class Interator, class Ref, class Ptr> class ReverseInterator { public: typedef ReverseInterator<Interator, Ref, Ptr> Self; ReverseInterator(Interator it) : _it(it) {} Self& operator++() { --_it; return *this; } Self operator++(int) { auto tmp = _it; --_it; return tmp; } Self& operator--() { ++_it; return *this; } Self operator--(int) { auto tmp = _it; ++_it; return tmp; } Ref operator*() { auto tmp = _it; return *(--tmp); } // 返回当前对象的地址 Ptr operator->() { return &(operator*()); } bool operator!=(const Self& s) const { return _it != s._it; } bool operator==(const Self& s) const { return _it == s._it; } private: Interator _it; // 对正向迭代器进行封装 };
因为rbegin()
是用end()
来构造的,rend()
使用begin()
来构造的,所以反向迭代器的解引用需要创建一个临时对象tmp
,再返回*(--tmp)
。
测试反向迭代器
注:以上的反向迭代器是用我们自己模拟实现的 list 来测试的!
👉总结👈
本篇博客主要讲解了什么是优先级队列、优先级队列的使用和模拟实现、仿函数以及反向迭代器的实现等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️