<C++> 优先级队列

目录

前言

一、priority_queue的使用

1. 成员函数

2. 例题

二、仿函数

三、模拟实现

1.  迭代器区间构造函数 && AdjustDown

2. pop

3.  push && AdjustUp

4. top

5. size

6. empty

 四、完整实现

总结


前言

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

优先级队列——priority_queue

1. 优先级队列是一种容器适配器,根据一些严格的弱排序标准,经过专门设计,其第一个元素始终是它所包含的最大元素。

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;

一、priority_queue的使用

1. 成员函数

2. 例题

215. 数组中的第K个最大元素

方法一:直接使用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<int>(),是一个匿名对象,而在priority_queue<>内部,第三个模板是greater<int>,是类型,不能加括号

二、仿函数

        我们在之前就遇到过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<T> 才是Less类的类型template<class T, class Container = vector<T>, class Compare = Less<T>>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容器

1.  迭代器区间构造函数 && AdjustDown

  • 采用封装容器vector的push_back尾插数据,因为默认是大堆,向下建堆,所以尾插数据后,将从最后一个元素的父亲结点—— (_con.size() - 1 - 1) / 2 开始向下调整。
  • 向下调整函数不用多说,在二叉树部分我们详细讲解过
namespace my_priority_queue
{// Less<T> 才是Less类的类型template<class T, class Container = vector<T>, class Compare = Less<T>>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;};
}

 2. pop

  • 堆的pop,将堆顶数据与最后一个数据进行交换,再进行pop_back,再将堆顶数据向下调整
		void pop(){//交换后,尾删,并向下调整swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}

 3.  push && AdjustUp

  • 尾插数据后,将该数据向上调整
		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);}

 4. top

  • 返回堆顶数据,返回值是 const T&
		const T& top(){return _con[0];}

 5. size

  • 返回vector的size()即可
		size_t size(){return _con.size();}

 6. empty

  • 直接调用vector的empty函数即可
		size_t size(){return _con.size();}

 四、完整实现

#pragma once
#include<iostream>
#include<vector>
#include<functional>
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<T> 才是Less类的类型template<class T, class Container = vector<T>, class Compare = Less<T>>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<int> 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:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/193106.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ansible 企业实战详解

一、ansible简介1. ansible是什么2.ansible的特点ansible的架构图 二、ansible 任务执行1、ansible 任务执行模式2、ansible 执行流程3、ansible 命令执行过程 二 .Ansible安装部署1.yum安装2.ansible 程序结构3、ansible配置文件查找顺序4、ansible配置文件5.ansible自动化配置…

electronjs入门-编辑器应用程序

我们将在Electron中创建一个新项目&#xff0c;如我们在第1章中所示&#xff0c;名为“编辑器”&#xff0c;我们将在下一章中使用它来创建编辑器&#xff1b;在index.js中&#xff0c;这是我们的主要过程&#xff1b;请记住为Electron软件包放置必要的依赖项&#xff1a; npm…

uniapp基础学习笔记01

文章目录 本博客根据黑马教程学习uniapp一、技术架构二、创建项目2.1 Hbuilder创建2.2 插件安装2.3 微信开发者工具配置与运行2.3.1 简单修改基础页面 2.4 pages.json和tabBar2.4.1 pages.json与tabBar配置2.4.2 案例 三、uniapp与原生开发的区别 本博客根据黑马教程学习uniapp…

安装node.js指定任意版本详解

Node.js是一种基于Chrome V8引擎的JavaScript运行时环境。它允许开发人员使用JavaScript编写服务器端和网络应用程序。与传统的JavaScript在浏览器中执行不同&#xff0c;Node.js使得JavaScript可以在服务器端运行。 Node.js具有以下特点&#xff1a; 1. 非阻塞式I/O&#xf…

Java设计模式-结构型模式-适配器模式

适配器模式 适配器模式应用场景案例类适配器模式对象适配器模式接口适配器模式适配器模式在源码中的使用 适配器模式 如图&#xff1a;国外插座标准和国内不同&#xff0c;要使用国内的充电器&#xff0c;就需要转接插头&#xff0c;转接插头就是起到适配器的作用 适配器模式&…

springboot国际化

1.环境配置 这里插入图片描述](https://img-blog.csdnimg.cn/024d6bc95623485eb6da4d998a892458.png) 2.文件配置 第一个默认环境 第二个英文环境 第三个中文环境 3.变量配置 调整语言 原理&#xff1a; 找到MessageSourceAutoConfiguration 中的 利用代碼的方式獲取国…

图论11-欧拉回路与欧拉路径+Hierholzer算法实现

文章目录 1 欧拉回路的概念2 欧拉回路的算法实现3 Hierholzer算法详解4 Hierholzer算法实现4.1 修改Graph&#xff0c;增加API4.2 Graph.java4.3 联通分量类4.4 欧拉回路类 1 欧拉回路的概念 2 欧拉回路的算法实现 private boolean hasEulerLoop(){CC cc new CC(G);if(cc.cou…

数字化转型时代,商业智能BI到底是什么?

据国际数据公司&#xff08;IDC&#xff09;预测&#xff0c;2025年时中国产生的数据量预计将达48.6ZB&#xff0c;在全球中的比例为27.8%。商业智能BI这一专为企业提供服务的数据类解决方案&#xff0c;仅2021年上半年在中国商业智能BI市场规模就达到了3.2亿美元&#xff0c;商…

java入门,从CK导一部分数据到mysql

一、需求 需要从生产环境ck数据库导数据到mysql&#xff0c;数据量大约100w条记录。 二、处理步骤 1、这里的关键词是生产库&#xff0c;第二就是100w条记录。所以处理数据的时候就要遵守一定的规范。首先将原数据库表进行备份&#xff0c;或者将需要导出的数据建一张新的表了…

Java绘图-第19章

Java绘图-第19章 1.Java绘图类 1.1Graphics类 Graphics类是用于绘制图形的抽象类&#xff0c;它是java.awt包中的一部分。Graphics类提供了各种方法&#xff0c;可以在图形上绘制各种形状、文本和图像。这些方法包括画线、画矩形、画椭圆、画弧、绘制图像等。 1.2Graphics2…

Android修行手册 - 阴影效果的几种实现以及一些特别注意点

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列点击跳转>ChatGPT和AIGC &#x1f449;关于作者 专…

3D造型渲染软件DAZ Studio mac中文版介绍

DAZ Studio mac是一款3D造型和渲染软件&#xff0c;由 Daz 3D 公司开发。它允许用户创建、编辑、动画化并渲染精美的数字图像与动画。DAZ Studio 还提供了一个虚拟的3D艺术家工作室环境&#xff0c;让用户可以轻松地设置场景、布置角色和应用材质。 用户可以通过 DAZ Studio 中…

8.查询数据

一、单表查询 MySQL从数据表中查询数据的基本语为SELECT语。SELECT语的基本格式是: SELECT {* | <字段列名>} [ FROM <表 1>, <表 2>… [WHERE <表达式> [GROUP BY <group by definition> [HAVING <expression> [{<operator>…

032-从零搭建微服务-定时服务(一)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff08;后端&#xff09;&#xff1a;mingyue: &#x1f389; 基于 Spring Boot、Spring Cloud & Alibaba 的分布式微服务架构基础服务中心 源…

C语言不可不敲系列:跳水比赛排名问题

目录 1题干&#xff1a; 2解题思路&#xff1a; 3代码: 4运行结果: 5总结: 1题干&#xff1a; 5位运动员参加了10米台跳水比赛&#xff0c;有人让他们预测比赛结果 A选手说&#xff1a;B第二&#xff0c;我第三&#xff1b; B选手说&#xff1a;我第二&#xff0c;E第四&am…

十九章总结

Graphics类 Graphics类是所有图形上下文的抽象基类&#xff0c;封装了Java支持的基本绘图操作所需的状态信息&#xff0c;主要包括颜色、字体、画笔 Graphics2D类 Graphics2D类继承Graphics类实现功能更加强大的绘图操作集合 绘制图形 在项目中创建一个类&#xff0c;是该…

nginx安装搭建

下载 免费开源版的官方网站&#xff1a;nginx news Nginx 有 Windows 版本和 Linux 版本&#xff0c;但更推荐在 Linux 下使用 Nginx&#xff1b; 下载nginx-1.14.2.tar.gz的源代码文件&#xff1a;wget http://nginx.org/download/nginx-1.14.2.tar.gz 我的习惯&#xff0…

【Linux进阶之路】一文吃透文件

前言 先来谈一下文件的共识 文件 内容 属性。 解释&#xff1a;文件在创建时就有基本属性&#xff0c;比如权限&#xff0c;文件名&#xff0c;文件的创建时间等基本信息。文件分为打开的文件与未被打开的文件。 解释&#xff1a;打开的文件由操作系统进行管理。未打开的文件…

JZ22:链表中倒数第k个结点

JZ22&#xff1a;链表中倒数第k个结点 题目描述&#xff1a; 输入一个链表&#xff0c;输出该链表中倒数第k个结点。 示例1 输入&#xff1a; 1,{1,2,3,4,5} 返回值&#xff1a; {5} 分析&#xff1a; 快慢指针思想&#xff1a; 需要两个指针&#xff0c;快指针fast&…

云课五分钟-03第一个开源游戏复现-贪吃蛇

前篇 云课五分钟-02第一个代码复现-终端甜甜圈C 视频 云课五分钟-03第一个开源游戏复现-贪吃蛇 一个终端的动态字符显然很难调动编程的积极性&#xff0c;那么更有趣的开源的游戏也许是一种更好的启发。 文本 蓝桥ROS机器人之绚丽贪吃蛇 如何在Linux下使用 DungeonRush-mast…