栈、队列、优先级队列的模拟实现
优先级队列的模拟实现
- 栈
- stack的模拟实现
- push()
- pop()
- top()
- size()
- empty()
- swap()
- stack总代码
- 队列
- queue的模拟实现
- push()
- pop()
- front()
- back()
- empty()
- size()
- swap()
- queue总代码
- 优先级队列(堆)
- push()
- pop()
- top()
- empty()
- size()
- swap()
- priority_queue总代码
- deque的了解
栈
在C++STL中栈并不属于容器一类,而属于适配器!
1、栈是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2、stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定
的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
3、stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下
操作:
empty:判空操作
back:获取尾部元素操作
push_back:尾部插入元素操作
pop_back:尾部删除元素操作
4、标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。
stack的模拟实现
对于栈的话,我们可以在其底层封装一个容器来实现:
template<class T, class Container=std::deque<T>>class stack{private:Container _con;//使用该容器来实现栈,具体使用那个类型的容器,完全由程序员自己决定!默认情况是使用缺省值(deque)双端队列来实现!}
对于stack我们可以不用写构造函数、拷贝构造函数、析构函数、重载赋值运算符!因为stack类的成员变量是个自定义类型,如果我们使用stack的默认构造函数的话,编译器会调用自定义类型的默认构造函数来初始化_con成员变量;同理如果使用默认拷贝构造的话,对于自定义类型也会调用自定义类型的拷贝构造函数;如果使用默认析构函数的话,对于自定义类型也会调用自定义类型的析构函数来释放空间,不会造成内存泄漏;赋值运算符同理!
push()
void push(const T& val)//对于栈的插入,也就是对于容器_con的尾插
{
_con.push_back(val);
}
pop()
void pop()//对于stack的删除就是对于容器_con的尾删除!
{assert(empty() == false);_con.pop_back();
}
top()
T Top()const//获取stack的栈顶元素就是获取容器_con的尾部元素
{assert(empty()==false);return _con.back();
}
size()
size_t size()const//获取stack的元素就是获取容器_con的元素个数
{return _con.size();
}
empty()
bool empty()const//判断stack是不是空就是判断容器_con是不是空
{ return _con.empty();
}
swap()
void swap(stack<T,Container>&st)//交换stack就是交换两个stack内部的容器_con
{_con.swap(st._con);
}
stack总代码
#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
#include<list>
#include<deque>
namespace MySpace
{template<class T, class Container=std::deque<T>>class stack{public:explicit stack(const Container&con= Container()):_con(con){}bool empty()const{ return _con.empty();}size_t size()const{return _con.size();}T Top()const{assert(empty()==false);return _con.back();}void push(const T& val){_con.push_back(val);}void pop(){assert(empty() == false);_con.pop_back();}void swap(stack<T,Container>&st){_con.swap(st._con);}private:Container _con;//组成stack的容器};
}
队列
1、 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端
提取元素。
2、队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的
成员函数来访问其元素。元素从队尾入队列,从队头出队列。
3、底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操
作:
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列
4、标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。
queue的模拟实现
队列与栈一样不属于容器,而属于适配器;因此队列的底层也是封装的一个容器;
因此队列也不需要自己写默认构造函数、拷贝构造函数、赋值运算符重载、析构函数,我们用编译器默认生成的就够了!
template <class T,class Container=std::deque<T>>//不建议使用vector作为容器,这样的话头插效率低
class queue{private:Container _con;}
push()
void push(const T& val)//对于queue的插入就是对于容器的尾插
{_con.push_back(val);
}
pop()
void pop()//对于queue的删除就是对于容器_con的头删
{assert(empty() == false);_con.erase(_con.begin());
}
front()
T front()const//获取queue队头元素,也就是获取容器_con首元素
{assert(empty() == false);return _con.front();
}
back()
T back()const//获取队尾元素,也就是获取容器_con的尾元素
{assert(empty() == false);return _con.back();
}
empty()
bool empty()const//对于queue的判空,也就是对于容器_con的判空
{return _con.empty();
}
size()
size_t size()const {//对于queue的大小就是容器_con的大小
return _con.size();
}
swap()
void swap(queue<T, Container> &q)//对于queue的交换就是交换底层的两个容器;
{
_con.swap(q._con);
}
queue总代码
#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
#include<list>
#include<deque>
namespace MySpace
{template <class T,class Container=std::deque<T>>//不建议使用vector作为容器,这样的话头插效率低class queue{public:explicit queue(const Container& con = Container()):_con(con){}bool empty()const{return _con.empty();}size_t size()const {return _con.size();}T front()const{assert(empty() == false);return _con.front();}T back()const{assert(empty() == false);return _con.back();}void push(const T& val){_con.push_back(val);}void pop(){assert(empty() == false);_con.erase(_con.begin());}void swap(queue<T, Container> &q){_con.swap(q._con);}private:Container _con;};
}
优先级队列(堆)
学过数据结构的读者应该直到数据结构中有一种叫做“堆”的数据结构,在C++中这种数据结构叫做“优先级队列”!关于堆的详细了解可以参考我的另一篇博客:二叉树和堆
优先级队列在STL中也是一个适配器!
因此对于优先级队列的定义我们可以参照stack和queue:
template <class T,class Container=std::vector<int>,class Compare=std::less<T>>
class priority_queue{private:Container _con;}
其中模板参数Container是容器类型;T是元素类型:Compare是仿函数的类型!用于控制建立大堆还是建立小堆;
其中根据STL的实现方法是:优先级队列默认是建大堆,如果需要建小堆的话,Compare模板参数需要传递greater <T>;
push()
优先级队列的push与stack、queue的插入不同,优先级队列push过后还需要调整当前的堆结构,使其再插入一个元素过后仍然保持堆结构,我们通常使用向上调整的算法建队!
void push(const T& val)
{//先插入_con.push_back(val);//向上调整AdjustUp(_con.size()-1);
}void AdjustUp(int child)//向上调整算法{Compare com;//实例化一个比较对象//如果Compare是个小于类的话,那么com实例化出来的就是小于对象,里面的operator(A,B)函数是比较A是否小于B的int parent = (child - 1) / 2;while (child>0){//if (_con[child] > _con[parent])//判断[parent]是否小于[child]if (com(_con[parent], _con[child]))//如果_con[parent]<_con[child]就建立小堆;如果_con[parent]>_con[child]就建立大堆;至于_con[parent]与_con[child]使用<比较还是>比较由我们传递的类型参数Compare来决定!{std::swap(_con[child],_con[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}}
pop()
优先级队列删除元素,是删除堆顶的元素,再删除堆顶元素过后,我们仍需要保持该结构是个堆;为了降低调整成本,优先级队列的删除操作的步骤一般都是,交换堆顶元素与堆底元素,然后堆的大小减1,然后再从对顶开始执行向下调整算法调整堆;
void pop()
{assert(empty()==false);//swapstd::swap(_con[0],_con[_con.size()-1]);//size--_con.pop_back();//向下调整AdjustDown(0);
}
void AdjustDown(int parent){Compare com;//实例化一个比较对象int child = 2 * parent + 1;while (child < _con.size()){//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])){std::swap(_con[child], _con[parent]);parent = child;child = 2 * parent + 1;}elsebreak;}}
top()
const T& top()const//返回堆顶元素,也就死返回容器_con的首元素
{return _con.front();
}
empty()
bool empty()const//判断一个优先级队列是不是空,就是判断一个容器是不是空
{return _con.empty();
}
size()
size_t size()const//求优先级队列的元素个数,就是返回容器的元素个数
{return _con.size();
}
swap()
void swap(priority_queue<T, Container, Compare>&pq)//两个优先级队列之间的交换就是交换底层的容器;
{_con.swap(pq._con);
}
priority_queue总代码
#pragma once
#include<iostream>
#include<vector>
#include<queue>
#include<deque>
#include<assert.h>
namespace MySpace
{template<class T>struct less//小于模板(用于建大堆){bool operator()(const T& v1, const T& v2)//这个函数的目的是为了比较v1是否小于v2{return v1 < v2;}};template<class T>struct less<T*>//小于模板偏特化,如果less模板的模板参数是指针,则用这个版本的less模板实例化对象{bool operator()( T* x, T * y)const{return *x < *y;}};template<class T>struct greater//大于模板(用于建小堆){bool operator()(const T& v1, const T& v2)const//这个函数的目的是为了比较v1是否大于v2{return v1 > v2;}};template<class T>struct greater<T*>//大于模板偏特化,如果greater模板的模板参数为指针类型,那么编译器就用这个版本的greater模板实例化对象!{bool operator()( T* x, T* y){return *x > *y;}};//默认建的是大堆,建大堆用less(小于)//建小堆用greter;template <class T,class Container=std::vector<int>,class Compare=MySpace::less<T>>class priority_queue{public:void push(const T& val){//先插入_con.push_back(val);//向上调整AdjustUp(_con.size()-1);}void pop(){assert(empty()==false);//swapstd::swap(_con[0],_con[_con.size()-1]);//size--_con.pop_back();//向下调整AdjustDown(0);}const T& top()const{return _con.front();}bool empty()const{return _con.empty();}void swap(priority_queue<T, Container, Compare>&pq){_con.swap(pq._con);}size_t size()const{return _con.size();}private:void AdjustDown(int parent){Compare com;//实例化一个比较对象int child = 2 * parent + 1;while (child < _con.size()){//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])){std::swap(_con[child], _con[parent]);parent = child;child = 2 * parent + 1;}elsebreak;}}void AdjustUp(int child){Compare com;//实例化一个比较对象int parent = (child - 1) / 2;while (child>0){//if (_con[child] > _con[parent])//判断[parent]是否小于[child]if (com(_con[parent], _con[child]))//判断[parent]是否小于[child]{std::swap(_con[child],_con[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}}Container _con;//容器};
}
deque的了解
通过前面的学习,我们知道了vector与list的优缺点:
vector:
优点:
1、支持随机访问;
2、尾插尾删效率高;
缺点:
1、中间和头部的插入删除,效率极低!
list
优点:
1、插入删除效率高;
缺点:
2、不支持随机访问;
那么有没有结合vector与list两个容器的优点的数据结构呢?
答案是有的!
双端队列(deque)就出现了:
什么是deque?
deque是一个两端开口,逻辑上空间是连续的数据结构,该数据结构的头部插入删除、尾部插入删除效率极高,时间复杂度为O(1);与list比较,空间利用率比较高!它的每个元素中不需要像list那样存储下一个节点的指针!
同时deque也支持像vector一样用[ ]来访问元素!
但是deque的底层真正实现并不是一块连续的空间,刚才我们也说了deque是在逻辑上连续的空间!真正的deque是由一段一段的连续空间组成起来的,有点类似于C语言中的二维数组!
其底层结构如下:
既然deque这么强,那么为什么还没替代掉vector和list呢?
deque的缺点:
与vector相比,头插头删效率的确高,不需要挪动元素,同时扩容代价下,deque的扩容只需要对中控数组进行扩容,不需要对存储元素的小段数组进行扩容,拷贝过来的也是这些小段数组的指针!
与list相比,空间利用率的确变高了,同时也支持了随机访问;
但是deque有一个致命的缺点:不适合遍历,因为deque迭代器在遍历的时候,会频繁的检测这次遍历是否移动到下一个小段数组中去,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,同时deque特别不适合中间来插入和删除数据,在中间删除和插入数据都需要大量的挪动数据,代价非常大!
而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构;
相关文章:

栈、队列、优先级队列的模拟实现
优先级队列的模拟实现栈stack的模拟实现push()pop()top()size()empty()swap()stack总代码队列queue的模拟实现push()pop()front()back()empty()size()swap()queue总代码优先级队列(堆)push()pop()top()empty()size()swap()priority_queue总代码deque的了解栈 在CSTL中栈并不属…...

JMM内存模型
JMM内存模型JMM内存模型定义三大特性原子性可见性有序性volatile语义JMM规则操作系统实现术语缓存一致性要求缓存一致性机制写传播事务串行化重排序as-if-serial 语义(像是有序的)happens-before 原则happens-before 原则的八大子原则内存屏障总结finalf…...

Linux- 系统随你玩之--玩出花活的命令浏览器-双生姐妹花
文章目录1、背景2、命令浏览器-双生姐妹花2.1、姐妹花简介2.2 、验名正身2.3、常用功能选项3、常用实操3.1、发送请求获取文件3.1.1、抓取页面内容到一个文件中3.1.2、多个文件下载3.1.3、下载ftp文件3.1.4、断点续传3.1.5、上传文件3.1.6、内容输出3.2 、利用curl测试接口3.3 …...

【深度学习】基于Hough变化的答题卡识别(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。⛳座右铭&#…...

Linux - 进程控制(创建和终止)
1.进程创建fork函数初识 在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。返回值:子进程返回0,父进程返回子进程id,出错返回-1getpid()获取子进程id,…...

依赖注入~
依赖注入之setter注入: 依赖注入是IOC具体的一种实现方式, 这是针对资源获取的方式角度来说的,之前我们是被动接受,现在IOC具体的实现叫做依赖注入,从代码的角度来说,原来创建对象的时候需要new࿰…...

【嵌入式硬件芯片开发笔记】HART协议调制解调芯片AD5700配置流程
【嵌入式硬件芯片开发笔记】HART协议调制解调芯片AD5700配置流程 XTAL_EN接地,CLK_CFG的两个引脚由同一个GPIO控制 初始时HART_CLK_CFG输出低电平 由RTS引脚控制调制/解调。当RTS处于高电平时,为解调(输入);否则为调…...
Go语言异步下载视频
异步下载mp4视频列表 下面是一个简单的Go语言示例,用于异步下载视频。我们将使用goroutines来实现异步下载,并使用sync.WaitGroup来等待所有下载任务完成。此示例依赖于net/http包来执行HTTP请求。 package mainimport ("fmt""io"…...

前缀树(字典树/Trie) -----Java实现
目录 一.前缀树 1.什么是前缀树 2.前缀树的举例 二.前缀树的实现 1.前缀树的数据结构 1.插入字符串 2.查找字符串 3.查找前缀 三.词典中最长的单词 1.题目描述 2.问题分析 3.代码实现 一.前缀树 1.什么是前缀树 字典树(Trie树)是一种树形…...
申请专利需要具备什么条件
申请专利需要具备什么条件 在我国,如果创造出来了新的发明都可以申请专利权,一旦申请成功之后,自己的发明就受到了法律的保护,任何人不得以违法的手段进行侵犯。那么申请专利需要具备什么条件?今天律赢时代网就为大家…...

【C++】一篇带你搞懂C++“引用”
前言在C语言的学习中,并没有引用这个概念,但是在C中,加入了引用这个概念,说明引用也是很重要的,但是我们怎么理解引用呢?我是这么理解的,例如在水浒传中,108个英雄好汉都是自己的外号…...

蓝桥杯刷题冲刺 | 倒计时19天
作者:指针不指南吗 专栏:蓝桥杯倒计时冲刺 🐾马上就要蓝桥杯了,最后的这几天尤为重要,不可懈怠哦🐾 文章目录1.抓住那头牛2.排列序数1.抓住那头牛 题目 链接: 抓住那头牛 - C语言网 (dotcpp.com…...

Java每日一练(20230321)
目录 1. 出现次数最多的字符 🌟 2. 最后一个单词的长度 🌟 3. 两数之和 🌟 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 出现次数最多的字符并…...

【三维几何学习】从零开始网格上的深度学习-3:Transformer篇(Pytorch)
本文参加新星计划人工智能(Pytorch)赛道:https://bbs.csdn.net/topics/613989052 从零开始网格上的深度学习-3:Transformer篇引言一、概述二、核心代码2.1 位置编码2.2 网络框架三、基于Transformer的网格分类3.1 分类结果3.2 全部代码引言 本文主要内容如下&#…...

一、基础算法3:二分 模板题+算法模板(数的范围,数的三次方根)
文章目录算法模板整数二分算法模板浮点数二分算法模板模板题数的范围原题链接题目题解数的三次方根原题链接题目题解算法模板 整数二分算法模板 bool check(int x) {/* ... */} // 检查x是否满足某种性质// 区间[l, r]被划分成[l, mid]和[mid 1, r]时使用: int b…...

Spring 源码解析 - Bean创建过程 以及 解决循环依赖
一、Spring Bean创建过程以及循环依赖 上篇文章对 Spring Bean资源的加载注册过程进行了源码梳理和解析,我们可以得到结论,资源文件中的 bean 定义信息,被组装成了 BeanDefinition 存放进了 beanDefinitionMap 容器中,那 Bean 是…...
移除元素(双指针)
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的…...

76.qt qml-QianWindow开源炫酷界面框架(支持白色暗黑渐变自定义控件均以适配)
界面介绍界面支持: 透明 白色 黑色 渐变 单色 静态图 动态图侧边栏支持:抽屉、带折叠、多模式场景控件已集成: 暗黑风格 高亮风格、并附带个人自定义控件及开源demo白色场景如下所示:单色暗黑风格如下所示:用户自定义皮肤如下所示:皮肤预览如下所示:b站入口:https://www.bilibi…...

Python生日蛋糕
目录 前言 底盘 蛋糕 蜡烛 祝福 前言 Hello,小伙伴们晚上好吖!前两天博主满20岁啦(要开始奔三辽呜呜呜),这几天收到了不少小伙伴们的祝福,浪漫的小博主想送给大家一份不一样的生日蛋糕,…...
QT 如何提高 Qt Creator 的编译速度
如何提高编译速度,貌似是一个老生常谈的话题。对于Qter而言,如何提高QT Creator 的编辑速度是一直都是大家所期盼的。本文也是查阅了各路大神的方法后整理出来的,希望对各位有所帮助。 1、在*.pro文件添加预编译机制 QT官方给出的示例&…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
命令行关闭Windows防火墙
命令行关闭Windows防火墙 引言一、防火墙:被低估的"智能安检员"二、优先尝试!90%问题无需关闭防火墙方案1:程序白名单(解决软件误拦截)方案2:开放特定端口(解决网游/开发端口不通)三、命令行极速关闭方案方法一:PowerShell(推荐Win10/11)方法二:CMD命令…...
Windows 下端口占用排查与释放全攻略
Windows 下端口占用排查与释放全攻略 在开发和运维过程中,经常会遇到端口被占用的问题(如 8080、3306 等常用端口)。本文将详细介绍如何通过命令行和图形化界面快速定位并释放被占用的端口,帮助你高效解决此类问题。 一、准…...
大模型真的像人一样“思考”和“理解”吗?
Yann LeCun 新研究的核心探讨:大语言模型(LLM)的“理解”和“思考”方式与人类认知的根本差异。 核心问题:大模型真的像人一样“思考”和“理解”吗? 人类的思考方式: 你的大脑是个超级整理师。面对海量信…...
第14节 Node.js 全局对象
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。 在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局…...