当前位置: 首页 > news >正文

C++初阶学习——探索STL奥秘——标准库中的priority_queue与模拟实现

1.priority_queque的介绍

1.priority_queue中文叫优先级队列。优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。

2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元 素)。

3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特 定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。

4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。

容器应该可以通过随机访问迭代器访问,并支持以下操作:

empty():检测容器是否为空

size():返回容器中有效元素个数

front():返回容器中第一个元素的引用

push_back():在容器尾部插入元素

pop_back():删除容器尾部元素

5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。

6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap和pop_heap来自动完成此操作。

2.priority_queue的基本使用

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。

注意: 默认情况下priority_queue是大堆。

2.1构造函数

优先级队列的构造方式有两种:直接构造一个空对象 和 通过迭代器区间进行构造

#include <iostream>
#include <vector>
#include <queue>	//注意:优先级队列包含在 queue 的头文件中using namespace std;int main()
{priority_queue<int> pq;	//直接构造一个空对象,默认为大堆cout << typeid(pq).name() << endl;	//查看类型return 0;
}

 注意默认仿函数为less,这个less决定了数据默认建大堆

通过迭代器区间构造对象

#include <iostream>
#include <vector>
#include <queue>	//注意:优先级队列包含在 queue 的头文件中using namespace std;int main()
{vector<char> vc = { 'a','b','c','d','e' };priority_queue<char, deque<char>, greater<char>> pq(vc.begin(), vc.end());	//现在是小堆cout << typeid(pq).name() << endl;	//查看类型cout << "==========================" << endl;while (!pq.empty()){//将小堆中的堆顶元素,依次打印cout << pq.top() << " ";pq.pop();}return 0;
}

注意:

将比较方式改为 greater 后,生成的是小堆

并且如果想修改比较方式的话,需要指明模板参数2 底层容器

因为比较方式位于模板参数3,不能跳跃缺省(遵循缺省参数规则) 

 2.2成员函数

#include <iostream>
#include <vector>
#include <queue>	//注意:优先级队列包含在 queue 的头文件中using namespace std;void Print(const priority_queue<int>& pq)
{cout << "是否为空:" << pq.empty() << endl;cout << "堆中的有效元素个数:" << pq.size() << endl;cout << "堆顶元素:" << pq.top() << endl;cout << "=================" << endl;
}int main()
{vector<int> v = { 27,15,19,18,28,34,65,49,25,37 };priority_queue<int> pq(v.begin(), v.end());	//默认生成大堆Print(pq);pq.push(10);pq.push(100);Print(pq);pq.pop();pq.pop();pq.pop();Print(pq);return 0;
}

2.3练习题 

思路:

利用数组建立大小为 k 的小堆,将剩余数据与堆顶值比较,如果大于,就入堆


为什么建小堆?因为此时需要的是最大的值,建大堆可能会导致次大的值无法入堆 

答案:

class Solution {
public:int findKthLargest(vector<int>& nums, int k) {priority_queue<int,vector<int>,greater<int>> q(nums.begin(),nums.begin()+k);for(int i=k;i<nums.size();i++){if(q.top()<nums[i]){q.pop();q.push(nums[i]);       }}return q.top();}
};

3.模拟实现优先级队列 

3.1构造函数

优先级队列 priority_queue 属于容器适配器的一种,像栈和队列一样,没有迭代器

同时也不需要实现自己的具体功能,调用底层容器的功能就行了

不过因为堆比较特殊,需要具备 向上调整 和向下调整 的能力,确保符合堆的规则

首先是基本框架

namespace bit
{template<class T,class Comtainer=vector<T> >class Priority_Queue{public:Priority_Queue() {}template<class InterIterator>private:Comtainer _con;};
}

接着是迭代器区间的默认构造

这个默认构造的功能是传递一段数据的迭代器区间给优先级队列,然后优先级队列会自动在内部将数据排列成大堆或者小堆 

namespace bit
{template<class T,class Comtainer=vector<T> >class Priority_Queue{public:Priority_Queue() {}template<class InterIterator>Priority_Queue(InterIterator first,InterIterator end){while (first != end){_con.push_back(*first);/*将数据推送到容器内部*/first++;}for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--){Adjust_Down(i);}}private:Comtainer _con;};
}

 3.2成员函数

		bool empty()const/*判断是否为空:复用底层结构的判空函数*/{return _con.empty();}const T& top()const/*获取堆顶元素:堆顶元素即第一个元素(二叉树的祖宗)*/{return _con[0];}size_t size()const/*获取优先级队列大小:复用获取大小的函数*/{return _con.size();}

注意: 以上三个函数均为涉及对象内容的改变,因此均使用 const 修饰 this 指针所指向的内容

在插入/删除数据后,需要确保堆能符合要求

大堆:父节点比子节点大

小堆:父节点比子节点小

因此每进行一次数据修改相关操作,都需要检查当前堆结构是否被破坏,这一过程称为调整

插入数据:尾插数据,然后向上调整

    void Adjust_Up(int child)
{int parent = child / 2 ;while(child>0)if (_con[parent] < _con[child]){std::swap(_con[parent], _con[child]);parent = child;child = parent-1/2;}elsebreak;
}void push(const T& x){_con.push_back(x);Adjust_Up(_con.size()-1);}

删除数据:将堆顶数据交换至堆底,删除堆底元素,再向下调整堆

这步至关重要,pop的时候将堆顶数据交换至堆底,再通过容器的成员函数pop_back删除堆底的元素,也就是删除掉容器存储的数据中最大或者最小的元素再从堆顶向下调整数据可以重新再这段数据中筛选出最大或者最小的元素推送到堆顶

		void Adjust_Down(int parent){int child = parent * 2 + 1;while (child < _con.size()){if (child+1<_con.size()&_con[child] <_con[child + 1]){child++;}if (_con[parent] <_con[child]){std::swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}elsebreak;}}void pop()const{std::swap(_con[0], _con[_con.size() - 1]);_con.pop_back();Adjust_Down(0);}

测试: 

3.3仿函数 

仿函数又名函数对象 function obiects

需要调用库#include<functional>

仿函数的主要作用是

借助类和运算符重载,做到同一格式兼容所有函数

这有点像函数指针

相比于函数指针又长又难理解的定义,仿函数的使用可谓是很简单了

下面是两个仿函数,作用是比较大小

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

此时 priority_queue 中的模板参数升级为3个,而参数3的缺省值就是Less

template<class T,class Comtainer=vector<T>,class Compare=Greater<T> >

 像这样,用仿函数生成的对象,将所有要用到比较大小的代码进行替换

Compare greater;
void Adjust_Down(int parent)
{int child = parent * 2 + 1;while (child < _con.size()){if (child+1<_con.size()&&greater(_con[child] , _con[child + 1])){child++;}if (greater(_con[parent] , _con[child])){std::swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}elsebreak;}
}

这样就能实现排序顺序的自由控制 

 

3.4特殊情况

在日期类中

class Date
{
public:Date(int year = 1970, 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 std::ostream& operator<<(std::ostream& _cout, const Date& d){_cout << d._year << "-" << d._month << "-" << d._day;return _cout;}
private:int _year;int _month;int _day;
};

此时会随机排序,因为我们传入的是指针 ,地址是随机的所以结果也是随机的

所以这里我们专门为它构造一个仿函数就可以了

//小于
template<class T>
struct LessPDate
{bool operator()(const T& p1, const T& p2){return (*p1) < (*p2);}
};//大于
template<class T>
struct GreaterPDate
{bool operator()(const T& p1, const T& p2){return (*p1) > (*p2);}
};

4.源码

源码链接

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【正负交替的分数求和】
  • OpenAI GPT o1技术报告阅读(3)-英文阅读及理解
  • 浅谈C++调用COM组件
  • 每日刷题(算法)
  • 论文阅读-《Attention is All You Need》
  • android13隐藏桌面底部白线
  • 54.【C语言】 字符函数和字符串函数(strncpy,strncat,strncmp函数)
  • 大厂程序员的健身之路
  • Mybatis-plus进阶篇(五)
  • 探索Docker:轻松进入容器并运行命令的实用指南
  • MYSQL表操作
  • powerbi-L8-导入数据时候的动态列
  • Vue3:实现div拖拽
  • 算法打卡:第十一章 图论part02
  • Flask + Swagger 完整指南:从安装到配置和注释
  • SegmentFault for Android 3.0 发布
  • 《剑指offer》分解让复杂问题更简单
  • 【笔记】你不知道的JS读书笔记——Promise
  • co.js - 让异步代码同步化
  • Gradle 5.0 正式版发布
  • Java-详解HashMap
  • Less 日常用法
  • php的插入排序,通过双层for循环
  • vue 配置sass、scss全局变量
  • Vue2.0 实现互斥
  • yii2权限控制rbac之rule详细讲解
  • Yii源码解读-服务定位器(Service Locator)
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 程序员该如何有效的找工作?
  • 从零开始学习部署
  • 好的网址,关于.net 4.0 ,vs 2010
  • 聊聊flink的BlobWriter
  • 前端工程化(Gulp、Webpack)-webpack
  • 如何在招聘中考核.NET架构师
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • ​业务双活的数据切换思路设计(下)
  • ###C语言程序设计-----C语言学习(3)#
  • #QT(一种朴素的计算器实现方法)
  • (+4)2.2UML建模图
  • (2022 CVPR) Unbiased Teacher v2
  • (MTK)java文件添加简单接口并配置相应的SELinux avc 权限笔记2
  • (纯JS)图片裁剪
  • (十三)MipMap
  • (原创)可支持最大高度的NestedScrollView
  • ****三次握手和四次挥手
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .cn根服务器被攻击之后
  • .jks文件(JAVA KeyStore)
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .Net 高效开发之不可错过的实用工具
  • .Net 路由处理厉害了
  • .NET 应用启用与禁用自动生成绑定重定向 (bindingRedirect),解决不同版本 dll 的依赖问题
  • .NET/C# 使窗口永不激活(No Activate 永不获得焦点)
  • .NET8使用VS2022打包Docker镜像
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证