stack,queue的模拟实现以及优先级队列

ops/2024/10/18 18:22:22/

这篇博客用来记录stack,queue的学习。

stack的模拟实现

stack的模拟实现比较简单,先上代码

#pragma once
#include<vector>
#include<list>
#include<deque>
#include<iostream>
using std::deque;
using namespace std;namespace bit
{template<class T, class Con = deque<T>>class stack{public:stack();void push(const T& x){_c.push_back(x);}void pop(){_c.pop_back();}T& top(){return _c.back(); //容器的尾在这里就是栈的栈顶}const T& top() const{return _c.back();}size_t size() const{return _c.size();}bool empty() const{return _c.empty();}private:Con _c;};
}

这里要提及的点是: 首先,看到模板类处传的默认参数是deque,<其实这里是容器适配器>
deque属于既包含vector的一些优点又有一些list的优点,但由于他样样通样样松导致他不被我们广泛使用,这里不对deque进行详细介绍。

queue的模拟实现

和stack的模拟实现类似,还是比较好写的,直接上代码:

#pragma once
#include<vector>
#include<list>
#include<deque>
#include<iostream>
using std::deque;
using namespace std;
namespace bit
{template<class T, class Con = deque<T>>class queue{public:queue();void push(const T& x){_c.push_back(x);}void pop(){_c.pop_front();}T& back(){return _c.back();}const T& back() const{return _c.back();}T& front(){return _c.front();}const T& front() const{return _c.front();}size_t size() const{return _c.size();}bool empty() const{return _c.empty();}private:Con _c;};
};

queue和stack对比起来,不同的点在于queue有了队头和队尾的区别。出现front和back访问队头和队尾的元素,而栈上是top获取栈顶元素。 队列在pop处是有差别的,queue的pop是对队头front进行删除。

接下来,介绍的才是重点:

优先级队列

优先级队列,就是我们对队列进行排序。 排序的过程类似于前面学过的堆,我们先来看几个操作。

template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
public:priority_queue();bool empty() {return c.empty();}size_t size(){return c.size();}const T& top() {return c[0];   // 这里我们默认用的容器是vector,因为我们后面都有进行排序,所以//c[0]就可以访问到第一个元素(最大或者最小根据所传类参数决定)}private:Container c;Compare comp;

上面代码有一个地方,可能会比较迷惑。 就是传参数后面有一个class Compare的东西。
这个东西有个名字,叫仿函数

仿函数是什么?

仿函数可以理解成对类对象进行了包装,使得类对象能够像函数一样去使用。 但本质是在类中进行了操作符的重载而实现一个类似于函数功能的作用。
那么,这里我们传入这个仿函数,在接下来插入或删除中就可以用上了!
了解了仿函数之后,我们再来看看优先级队列的插入和删除操作

插入操作,我们进行排序类似于堆,所以我们回忆一下大堆小堆的插入删除。
以前我们向上调整时,判断条件是对parent和child的值进行判断。
但如果我们改变排序的顺序,例如从升序改成降序,我们这里就可能有多处代码要改。那为了简便,我们有了仿函数的概念。
仿函数该怎么具体使用,让我们来看看:
首先,我们要明确的点是,之前说了,仿函数其实是一个类。我们用仿函数,是因为他里面通过运算符重载实现了我们想要的操作
那么这里我们先定义这样的两个类

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

那么,我们了解这个点之后,我们再将他放入代码中,去实现push和pop操作
下面这块代码紧接上面的代码

template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
public:void Adjustup(size_t child){Compare com;  //这里很重要! 我们通过传入的Compare类创建了这样一个对象,//之后利用这个对象,去比较大小int parent = (child - 1) / 2;while (child > 0){//这里就是不同点了,我们通过传入两个参数来比较大小!!// 这里三种写法都是对的,上面两种采用的是匿名对象的方式, 但还是推荐使用第三种!//if(Compare().operator()(c[parent],c[child]))//if(Compare()(c[parent],c[child]))//if(c[parent]<c[child])   //以前我们的方法if (com(c[parent], c[child])) {swap(c[parent], c[child]);child = parent;parent = (child - 1) / 2;}else{break;}}}void push(const T& x){c.push_back(x);Adjustup(c.size() - 1);  // 这里传参不要传错了,不然会出现问题}void Adjustdown(size_t parent){Compare com;int child = parent * 2 + 1;while (child < c.size()){if (child + 1 < c.size() && com(c[child],c[child + 1]))child = child + 1;if (com(c[parent], c[child])){swap(c[child], c[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void pop(){swap(c[0], c[c.size() - 1]);c.pop_back();Adjustdown(0);}private:Container c;Compare comp;
};

可以看到,我们比较大小是直接通过对象,来充分使用operator()来比较大小,我们注意写好传入参数的先后顺序, 之后只需要控制传入的类类型就可以轻松控制排序。
接下来看看这段代码实现的结果吧!

void test1()
{// 用默认的less排降序(大堆)bit::priority_queue<int> pq1;pq1.push(10);pq1.push(20);pq1.push(30);pq1.push(40);while (!pq1.empty()){cout << pq1.top() << ' ';pq1.pop();}cout << endl;// 用greater排升序(小堆)bit::priority_queue<int, vector<int>, bit::greater<int>> pq2;pq2.push(10);pq2.push(20);pq2.push(30);pq2.push(40);while (!pq2.empty()){cout << pq2.top() << ' ';pq2.pop();}cout << endl;
}

在这里插入图片描述
接着,我们进行一下升级。 我们传入Date日期类类型,看看可不可以同样实现

对Date日期类进行排序

// 首先这一段是对Date类的操作
class Date
{
public:friend ostream& operator<<(ostream& _cout, const Date& d);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);}
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}template<class T>
class greater  //这个类用来排小堆
{public:bool operator()(const T& x, const T& y){return x > y;}
void test2()
{// 日期类进行排升序   bit::priority_queue<Date, vector<Date>, greater<Date>> pq;Date d1(2024, 4, 8);pq.push(d1);pq.push(Date(2024, 4, 10));pq.push({ 2024, 2, 15 });while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;

在这里插入图片描述
那么这依然是能实现的。

接下来有一个特殊情况,如果我们传入的是指针类型呢?那还能不能比较大小呢?

指针类型的比较大小

bit::priority_queue<Date*, vector<Date*>> pqptr;
pqptr.push(new Date(2024, 4, 14));
pqptr.push(new Date(2024, 4, 11));
pqptr.push(new Date(2024, 4, 15)); while (!pqptr.empty())
{cout << *(pqptr.top()) << " ";pqptr.pop();
}
cout << endl;  

很遗憾,这里并不能实现我们的效果。
因为如此,毕竟的是指针本身的大小, 而这个大小属于随机的,因此比较出来结果也会是个随机值。因此我们必须为此写一个指针解引用去比较的仿函数

在上一段代码前我们需要传入

// 这里没有给模版(template<>),所以后面调用这个类时,就不需要传入对应的类模板参数,
// 直接调用这个类就可以了
class GreaterPDate
{
public:bool operator()(const Date* p1, const Date* p2){return *p1 > *p2;    // 这里是大于,就是后面形成小堆,(这个类叫Greater)}
};bit::priority_queue<Date*, vector<Date*>, GreaterPDate> pqptr;
pqptr.push(new Date(2024, 4, 14));
pqptr.push(new Date(2024, 4, 11));
pqptr.push(new Date(2024, 4, 15));  
while (!pqptr.empty())
{cout << *(pqptr.top()) << " ";pqptr.pop();
}
cout << endl;

在这里插入图片描述
如此,我们才能保证每次比较的是指针解引用后的结果。这样才能稳定形成结果!
当然,这里我写的是升序的代码,老样子,把Greater改成less的代码,就是降序的了!


http://www.ppmy.cn/ops/15593.html

相关文章

Youtube DNN

目录 1. 挑战 2. 系统整体结构 3.召回 4. 排序 5. 训练和测试样本的处理 1. 挑战 &#xff08;1&#xff09;规模。很多现有的推荐算法在小规模上效果好&#xff0c;但Youtobe规模很大。 &#xff08;2&#xff09;新颖度。Youtobe语料库是动态的&#xff0c;每秒都会有…

怎样用PHP语言实现远程控制三路开关

怎样用PHP语言实现远程控制三路开关呢&#xff1f; 本文描述了使用PHP语言调用HTTP接口&#xff0c;实现控制三路开关&#xff0c;三路开关可控制三路照明、排风扇等电器。 可选用产品&#xff1a;可根据实际场景需求&#xff0c;选择对应的规格 序号设备名称厂商1智能WiFi墙…

就业班 第三阶段(负载均衡) 2401--4.19 day3

二、企业 keepalived 高可用项目实战 1、Keepalived VRRP 介绍 keepalived是什么keepalived是集群管理中保证集群高可用的一个服务软件&#xff0c;用来防止单点故障。 ​ keepalived工作原理keepalived是以VRRP协议为实现基础的&#xff0c;VRRP全称Virtual Router Redundan…

Laravel 6 - 第十三章 请求

​ 文章目录 Laravel 6 - 第一章 简介 Laravel 6 - 第二章 项目搭建 Laravel 6 - 第三章 文件夹结构 Laravel 6 - 第四章 生命周期 Laravel 6 - 第五章 控制反转和依赖注入 Laravel 6 - 第六章 服务容器 Laravel 6 - 第七章 服务提供者 Laravel 6 - 第八章 门面 Laravel 6 - …

机器人流量激增:恶意机器人活动升级与新型规避技术挑战企业安全防御

近日&#xff0c;根据Cyber News引用Thales Imperva Bad Bot发布的最新研究报告&#xff0c;揭示了一个令人警醒的现象&#xff1a;2023年&#xff0c;互联网总流量中的49.6%由机器人贡献&#xff0c;相较于上一年增长了2%&#xff0c;创下了自2013年监测以来的历史新高。这一显…

03 - 伪目标

---- 整理自狄泰软件唐佐林老师课程 文章目录 1. 思考2. 伪目标的引入2.1 伪目标的语法&#xff1a;先声明&#xff0c;后使用2.2 伪目标的妙用&#xff1a;规则调用&#xff08;函数调用&#xff09;2.3 绕开 .PHONY 关键字定义伪目标 1. 思考 Makefile 中的 目标 究竟是什么&…

GPT-SoVITS声音训练报错ZeroDivisionError: division by zero

环境: GPT-SoVITS-0421 问题描述: GPT-SoVITS声音训练报错ZeroDivisionError: division by zero Traceback (most recent call last):File "E:\GPT-SoVITS-0421\GPT-SoVITS-0421\GPT_SoVITS\s2_train.py", line 600, in <module>main()File "E:\GPT…

初入数据库

SQL&#xff1a;操作关系型数据库的编程语言&#xff0c;定义了一套操作关系型数据库的统一标准。 DDL&#xff08;Data Definition Language&#xff09;数据定义语言 数据库 show databases;create database db01;use db01;select database(); 显示当前使用的数据库drop d…