【C++从0到王者】第十八站:手把手教你写一个简单的优先级队列

文章目录

  • 一、优先级队列简介
  • 二、优先级队列的接口说明
    • 1.基本介绍及其使用
    • 2.构造函数
    • 3.求数组中第k个最大的元素
  • 三、手撕优先级队列
  • 四、仿函数
    • 1.仿函数介绍
    • 2.优先级队列添加仿函数
    • 3.需要自己写仿函数的情形
  • 五、优先级队列完整代码

一、优先级队列简介

在这里插入图片描述

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

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

优先级队列是作为容器适配器实现的,这些适配器是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。元素从特定容器的“后面”弹出,这被称为优先级队列的顶部。

底层容器可以是任何标准容器类模板或其他特定设计的容器类。容器必须可以通过随机访问迭代器访问,并支持以下功能操作:empty、size、front、push_back、pop_back

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

为了始终在内部保持堆结构,需要支持随机访问迭代器。这是由容器适配器在需要时自动调用算法函数make_heap、push_heap和pop_heap自动完成的。

二、优先级队列的接口说明

1.基本介绍及其使用

优先级队列的接口有如下几种。对于优先级队列我们默认是它的大的数优先级高。其底层是一个堆。也就是说,我们默认是大堆,所以大的数优先级高。如果是一个小堆,那么就是小的优先级高。
在这里插入图片描述

我们来简单的使用一下这些接口

void test_priority_queue()
{priority_queue<int> pq;pq.push(1);pq.push(10);pq.push(2);pq.push(51);pq.push(4);while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;
}

在这里插入图片描述

可以看到,默认是一个大堆,但是我们会注意到,它库里面默认传的是less,但是却是一个大堆,这里需要额外注意一下。
在这里插入图片描述
所以我们如果想要是一个小堆的话,我们需要将这个less替换为greater
在这里插入图片描述

在这里我们传的less,greater这些也称之为仿函数。也就是说,通过仿函数控制实现大小堆.

除此之外,这里除了可以传vector以外,还可以传递deque,但是由于堆需要大量访问[]运算符,所以deque的效率不高。

2.构造函数

对于它的构造函数也是比较简单的,如下所示,可以无参构造,也可以用迭代器区间进行初始化。
在这里插入图片描述

3.求数组中第k个最大的元素

题目链接:数组中第k个最大元素

这道题其实就是top-k问题,由于优先级队列就是一个堆,所以我们直接使用优先级队列可以很轻松的完成这道题

下面是建大堆的方法

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

下面是建小堆的方法,注意我们的模板参数。

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

三、手撕优先级队列

如下代码所示,对于优先级队列,主要还是堆的逻辑的实现。即堆的构造,向上调整和向下调整。

	template<class T, class Container = vector<T>>class priority_queue{private:void AdjustDown(int parent){int child = parent * 2 + 1;while (child<_con.size()){if (child + 1 < _con.size() && _con[child] < _con[child + 1]){child++;}if (_con[child] > _con[parent]){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void AdjustUp(int child){int parent = (child - 1) / 2;while (child > 0){if (_con[child] > _con[parent]){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}public:template<class InputIterator>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);}}priority_queue(){}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}void push(const T& val){_con.push_back(val);AdjustUp(_con.size() - 1);}const T& top(){return _con[0];}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};

四、仿函数

1.仿函数介绍

我们知道对于优先级队列可以用仿函数改变其是大堆还是小堆。根据底层逻辑可知,仿函数应该就是改变了大小比较。才改变的行为。我们可以写一个简单的仿函数类

如下所示就是一个最简单的仿函数

	class less{public:bool operator()(int x, int y){return x < y;}};

这样我们就可以类似于一个函数一样进行比较大小了,仿函数即函数对象,可以让类对象像函数一样使用
在这里插入图片描述

我们可以继续将这个仿函数扩展成模板,如下所示,这样更加贴近于我们的使用

	template<class T>class less{public:bool operator()(const T& x, const T& y){return x < y;}};

在这里插入图片描述

有了仿函数,我们就可以在前面的优先级队列中使用仿函数来切换大堆小堆了。在C语言中,我们想要实现这个功能只有使用函数指针。而这个仿函数就刚好可以替换掉函数指针。因为函数指针的弊端太明显了,它太过于复杂了,可读性不好。

2.优先级队列添加仿函数

#pragma once
namespace Sim
{template<class T, class Container = vector<T>, class Compare = less<T>>class priority_queue{private:void AdjustDown(int parent){Compare com;int child = parent * 2 + 1;while (child<_con.size()){if (child + 1 < _con.size() && com(_con[child], _con[child + 1])){child++;}if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void AdjustUp(int child){Compare com;int parent = (child - 1) / 2;while (child > 0){if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}public:template<class InputIterator>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);}}priority_queue(){}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}void push(const T& val){_con.push_back(val);AdjustUp(_con.size() - 1);}const T& top(){return _con[0];}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};template<class T>class less{public:bool operator()(const T& x, const T& y){return x < y;}};template<class T>class greater{public:bool operator()(const T& x, const T& y){return x > y;}};
}

接下来我们可以去测试一下我们的优先级队列,用内置类型是没有什么问题的,我们可以使用自定义类型来进行测试,比如将我们之前所写的日期类给导入进来

这里直接给出我们的代码

Date.h

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{//友元函数声明friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);
public:Date(int year = 1, int month = 1, int day = 1);void Print() const{cout << _year << '-' << _month << '-' << _day << endl;}bool operator<(const Date& x) const;bool operator==(const Date& x) const;bool operator<=(const Date& x) const;bool operator>(const Date& x) const;bool operator>=(const Date& x) const;bool operator!=(const Date& x) const;int GetMonthDay(int year, int month) const;Date& operator+=(int day);Date& operator-=(int day);Date operator+(int day) const;Date operator-(int day) const;Date& operator++();Date operator++(int);Date& operator--();Date operator--(int);int operator-(const Date& d) const;private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d); 

Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
Date::Date(int year, int month, int day)
{if (month > 0 && month < 13&& day>0 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}else{cout << "非法日期" << endl;assert(false);}
}
bool Date::operator<(const Date& x) const
{if (_year < x._year){return true;}else if (_year == x._year && _month < x._month){return true;}else if (_year == x._year && _month == x._month && _day < x._day){return true;}return false;
}
bool Date::operator==(const Date& x) const
{return (_year == x._year) &&(_month == x._month) &&(_day == x._day);
}
bool Date::operator<=(const Date& x) const
{return (*this < x) || (*this == x);
}
bool Date::operator>(const Date& x) const
{return !(*this <= x);
}
bool Date::operator>=(const Date& x) const
{return !(*this < x);
}
bool Date::operator!=(const Date& x) const
{return !(*this == x);
}
int Date::GetMonthDay(int year, int month) const
{if (month <= 12 && month >= 1){const static int ArrDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0))){return 29;}return ArrDay[month];}cout << "非法日期" << endl;return -1;
}
Date& Date::operator+=(int day)
{if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month > 12){_month -= 12;_year++;}}return *this;
}
Date& Date::operator-=(int day)
{if (day < 0){return *this += -day;}_day -= day;while (_day <= 0){_month--;if (_month <= 0){_month += 12;_year--;}_day += GetMonthDay(_year, _month);}return *this;
}
Date Date::operator+(int day) const
{Date tmp = *this;return tmp += day;
}
Date Date::operator-(int day) const
{Date tmp = *this;return tmp -= day;
}
Date& Date::operator++()
{*this += 1;return *this;
}
Date Date::operator++(int)
{Date tmp = *this;*this += 1;return tmp;
}
Date& Date::operator--()
{*this -= 1;return *this;
}
Date Date::operator--(int)
{Date tmp = *this;*this -= 1;return tmp;
}
int Date::operator-(const Date& d) const
{Date max = *this;Date min = d;int flag = 1;if (max < min){max = d;min = *this;flag = -1;}int n = 0;while (max != min){min++;n++;}return flag * n;
}
//流插入不能写成成员函数
//因为Date对象默认占用第一个参数,就是做了左操作数
//写出来就是下面的样子,不符合我们的使用习惯
//d1<<cout;
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}
istream& operator>>(istream& in, Date& d)
{int year, month, day;in >> year >> month >> day;if (month > 0 && month < 13&& day>0 && day <= d.GetMonthDay(year, month)){d._year = year;d._month = month;d._day = day;}else{cout << "非法日期" << endl;assert(false);}return in;
}

然后我们用下面的代码进行测试

void Date_test()
{//Sim::priority_queue<Date, vector<Date>, greater<Date>> pq;Sim::priority_queue<Date> pq;pq.push(Date(2023, 7, 1));pq.push(Date(2023, 7, 10));pq.push(Date(2023, 7, 12));pq.push(Date(2023, 7, 13));while (!pq.empty()){cout << pq.top();pq.pop();}cout << endl;
}

符合我们的预期
在这里插入图片描述

3.需要自己写仿函数的情形

我们的上面的仿函数是模拟库里面的行为,上面的仿函数在库里面早已给出,我们无需自己动手写。但是有时候我们也需要自己去写一个仿函数。

如下测试用例,我们此时存储的是一个指针,而不是一个对象

void Date_test1()
{//Sim::priority_queue<Date, vector<Date>, greater<Date*>> pq;Sim::priority_queue<Date*> pq;pq.push(new Date(2023, 7, 1));pq.push(new Date(2023, 7, 10));pq.push(new Date(2023, 7, 12));pq.push(new Date(2023, 7, 13));while (!pq.empty()){cout << *(pq.top());pq.pop();}cout << endl;
}

在这里插入图片描述

这时候我们的比较逻辑就会出问题,我们只能自己写一个去比较指针指向的内容

如下代码所示

struct LessPDate
{bool operator()(Date* x, Date* y){return *x < *y;}
};

在这里插入图片描述

五、优先级队列完整代码

#pragma once
namespace Sim
{template<class T, class Container = vector<T>, class Compare = less<T>>class priority_queue{private:void AdjustDown(int parent){Compare com;int child = parent * 2 + 1;while (child<_con.size()){if (child + 1 < _con.size() && com(_con[child], _con[child + 1])){child++;}if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void AdjustUp(int child){Compare com;int parent = (child - 1) / 2;while (child > 0){if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}public:template<class InputIterator>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);}}priority_queue(){}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}void push(const T& val){_con.push_back(val);AdjustUp(_con.size() - 1);}const T& top(){return _con[0];}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};template<class T>class less{public:bool operator()(const T& x, const T& y){return x < y;}};template<class T>class greater{public:bool operator()(const T& x, const T& y){return x > y;}};
}

好了,本期内容就到这里了
如果对你有帮助,不要忘记点赞加收藏哦!!!

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

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

相关文章

Java经典面试题总结(一)

Java经典面试题总结&#xff08;一&#xff09; 题一&#xff1a;Java编译运行原理题二&#xff1a;JDK&#xff0c;JVM&#xff0c;JRE三者之间的关系题三&#xff1a;谈一下对冯诺依曼体系的了解题四&#xff1a;重载与重写的区别题五&#xff1a;拆箱装箱是指什么&#xff1…

Netty 入门指南

文章目录 前言Netty介绍Netty发展历程Netty核心组件实现HTTP服务器总结 前言 上文《BIO、NIO、IO多路复用模型详细介绍&Java NIO 网络编程》介绍了几种IO模型以及Java NIO&#xff0c;了解了在网络编程时使用哪种模型可以提高系统性能及效率。即使Java NIO可以帮助开发人员…

44.实现爱尔兰B公式计算并输出表格(matlab程序)

1.简述 1.话务量定义 话务量指在一特定时间内呼叫次数与每次呼叫平均占用时间的乘积。 话务量反映了电话负荷的大小&#xff0c;与呼叫强度和呼叫保持时间有关。呼叫强度是单位时间内发生的呼叫次数&#xff0c;呼叫保持时间也就是占用时间。 话务量计算方法 话务量公式为…

低功耗LoRaWAN国产低功耗LoRa+RF射频前端芯片XD6500S

目录 典型应用XD6500S简介芯片特性 LoRa系列选型参考 LoRa是为低数据速率、远距离距离和超低功耗而优化的扩频协议&#xff0c;用于LPWAN应用程序的通信。 典型应用 一、智慧农业   智慧农业大田解决方案利用传感设备、自动化控制设备、气象站实时监测采集田间土壤墒情、气象…

HTTP协议——应用层

HTTP协议 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的. 这种约定, 就是 应用层协议 HTTP简介 HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;协议又叫做超文本传输协议&#xff0c;是一个简单的请求-响应协议&#xff0c;HTTP通常运行…

MyBatis简介及环境配置

文章目录 一、什么是MyBatis二、MyBatis开发环境配置1.创建数据库表2.添加MyBatis框架支持3.配置连接字符串和MyBatis4.添加业务代码流程 一、什么是MyBatis MyBatis是一种持久层框架&#xff0c;也是一种ORM框架&#xff08;Object Relational Mapping即对象关系映射&#xf…

【AutoLayout案例1-按钮居中显示 Objective-C语言】

一、按钮居中显示 1.接下来,我们就用这个autoLayout,自动布局,给大家写一个,实现几个案例,给大家看一下 那么,首先,第一个,大家注意, 当我们使用autoLayout,自动布局的时候,我们新建一个项目, 这个新建的项目,里面有一个控制器,这个控制器,是不是默认,是四四…

基于短信宝API零代码实现短信自动化业务

场景描述&#xff1a; 基于短信宝开放的API能力&#xff0c;实现在特定事件&#xff08;如天气预警&#xff09;或定时自动发送短信&#xff08;本文以定时群发短信为例&#xff09;。通过Aboter平台如何实现呢&#xff1f; 使用方法&#xff1a; 首先创建一个IPaaS流程&…

iPhone苹果手机地震预警功能怎么开启?

iPhone苹果手机地震预警功能怎么开启&#xff1f; 1、打开iPhone苹果手机设置&#xff1b; 2、在iPhone苹果手机设置内找到辅助功能&#xff1b; 3、在辅助功能内找到触控&#xff1b; 4、在iPhone苹果手机辅助功能触控内找到振动&#xff0c;如果是关闭状态请启&#xff1b; …

C++ STL vector

目录 一.认识vector 二.vector的使用 1.vector的构造函数 2.vector的迭代器 2.1 begin&#xff08;&#xff09;&#xff0c;end&#xff08;&#xff09; 2.2 rbegin&#xff08;&#xff09;&#xff0c;rend&#xff08;&#xff09; 2.3 迭代器初始化对象 3. vector…

Linux中安装Tomcat

Linux安装Tomcat 操作步骤: 1、使用FinalShell自带的上传工具将Tomcat的二进制发布包上传到Linux 2、解压安装包&#xff0c;命令为tar -zxvf apache-tomcat-7.0.57.tar.gz -C /usr/local 3、进入Tomcat的bin目录启动服务&#xff0c;命令为sh startup.sh或者./ startup.sh …

leetcode每日一练-第278题-第一个错误的版本

一、思路 二分查找——因为它可以快速地将版本范围缩小一半&#xff0c;从而更快地找到第一个坏版本。 二、解题方法 维护一个左边界 left 和一个右边界 right&#xff0c;在每一步循环中&#xff0c;我们计算中间版本 mid&#xff0c;然后检查它是否是坏版本。如果是坏版本…

利用Overleaf使用Latex插入算法伪代码

目录 一个简单的例子&#xff1a; 样式一&#xff08;algorithm2e算法&#xff09;&#xff1a; 样例二&#xff08;algorithm2e算法&#xff09;&#xff1a; 样式三&#xff08;algorithm算法&#xff09;&#xff1a; 下面详细讲解algorithm2e算法的使用 1、宏包参数的…

2023华数杯数学建模C题完整5问代码思路分析

目前已经写出2023华数杯C题母亲身心健康对婴儿成长的影响全部5问的完整代码和42页论文&#xff08;正文30页&#xff0c;论文部分摘要如下&#xff1a; 本文共解决了五个问题&#xff0c;涉及婴儿行为特征、睡眠质量与母亲的身体指标和心理指标的关系&#xff0c;以及如何优化…

使用Socket实现TCP版的回显服务器

文章目录 1. Socket简介2. ServerSocket3. Socket4. 服务器端代码5. 客户端代码 1. Socket简介 Socket&#xff08;Java套接字&#xff09;是Java编程语言提供的一组类和接口&#xff0c;用于实现网络通信。它基于Socket编程接口&#xff0c;提供了一种简单而强大的方式来实现…

【MATLAB第66期】#源码分享 | 基于MATLAB的PAWN全局敏感性分析模型(有条件参数和无条件参数)

【MATLAB第66期】#源码分享 | 基于MATLAB的PAWN全局敏感性分析模型&#xff08;有条件参数和无条件参数&#xff09; 文献参考 Pianosi, F., Wagener, T., 2015. A simple and efficient method for global sensitivity analysis based on cumulative distribution functions.…

zuul实现黑名单,request多次读取问题,stream close

一&#xff0c;背景及设计 1.需要在网关实现黑名单功能&#xff0c;实现拦截指定接口。黑名单用户&#xff0c;会加入指定黑名单列表&#xff0c;关联对应功能&#xff0c;如用户登录&#xff0c;用户下单&#xff0c;用户接单。 2.表设计 平台表&#xff1a;不同系统 黑名单…

Grafana 曲线图报错“parse_exception: Encountered...”

问题现象 配置的Grafana图报错如下&#xff1a; 原因分析 点开报错&#xff0c;可以看到报错详细信息&#xff0c;是查询语句的语法出现了异常。 变量pool的取值为None 解决方案 需要修改变量pool的查询SQL&#xff0c;修改效果如下&#xff1a; 修改后&#x…

HTML5注册页面

分析 注册界面实际上是一个表格&#xff08;对齐&#xff09;&#xff0c;一行有两个单元格。 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevic…

行业追踪,2023-08-08

自动复盘 2023-08-08 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…