C++-----stack和queue

本期我们来学习stack和queue

目录

stack介绍

栈的使用

栈的模拟实现

queue介绍

队列的使用 

 队列的模拟实现

deque

优先级队列

 模拟实现

仿函数

全部代码


stack介绍

1. stack 是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2. stack 是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部( 即栈顶 ) 被压入和弹出。
3. stack 的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
empty :判空操作
back :获取尾部元素操作
push_back :尾部插入元素操作
pop_back :尾部删除元素操作
4. 标准容器 vector deque list 均符合这些需求,默认情况下,如果没有为 stack 指定特定的底层容器,默认情况下使用deque

 我们发现,stack模板参数和我们之前的容器是不同的,我们现在只要知道,我们的栈和队列,是容器适配器,是用容器转换出来的,适配器的本质是一种复用

栈的使用

 这些接口根据我们目前的水平是可以轻松看懂的

并且我们发现,它是没有迭代器的,不支持我们随便进行遍历的,因为要保持后进先出,队列也是一样的

 

 使用起来是非常简单的

栈的模拟实现

我们前面说过适配,我们来完成一个链式栈和数组栈秒切换

namespace bai
{template<class T,class Container>class stack{public:void push(const T& x){}private:Container _con;};void test() {stack<int, vector<int>> st1;stack<int, list<int>> st2;}
}

我们加一个模板参数就可以了

        void push(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}T& top(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}

这些接口我们全部复用即可,这就是适配器,非常方便

其中top我们使用back接口

list和vector都有back和front接口,我们直接使用

容器适配器就是,数据是容器管理,我们对容器进行封装,管理,改造等等

测试一下,也没有问题

不过此时我们还有一个问题,我们每次都要传两个模板参数,非常麻烦,库里面是不需要的

 它给的是类型,给了一个deque,deque我们后面解释

我们先加一个缺省参数,给一个vector即可

此时不写默认就是vector的栈

 另外,我们的栈是不需要构造函数,拷贝构造这些,因为我们是自定义类型,回去调用自己的构造,拷贝构造等等

queue介绍

1. 队列是一种容器适配器,专门用于在 FIFO 上下文 ( 先进先出 ) 中操作,其中从容器一端插入元素,另一端提取元素。
2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类, queue 提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty :检测队列是否为空
size :返回队列中有效元素的个数
front :返回队头元素的引用
back :返回队尾元素的引用
push_back :在队列尾部入队列
pop_front :在队列头部出队列
4. 标准容器类 deque list 满足了这些要求。默认情况下,如果没有为 queue 实例化指定容器类,则使用标准容器deque

队列的使用 

同样的,这些接口都很简单,这里就不再演示

 队列的模拟实现

队列用vector适配是不好的,但我们也可以强制适配

vector是没有提供头删的

不过我们可以使用erase来强制适配

        void push(const T& x){_con.push_back(x);}void pop(){//_con.pop_front();_con.erase(_con.begin());}T& front(){return _con.front();}T& back(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}

修改一下top,为front,再加一个back接口即可

测试一下也没有问题 

但是这样强制适配是不好的

库里面是支持list,但不支持vector的 

因为顺序表的头删效率是很低的

所以queue的默认容器我们使用list 

deque

deque是一个双端队列,不过它虽然看起来是队列,但其实不是

队列的特点是先进先出,但deque不是,它是一个双向开口的,两边都可以插入删除,而且他是一个随机迭代器

它就像一个vector和list合集的六边形战士,可以像数组一样访问,还有头插头删,尾插尾删

 

我们简单用一下 

那deque这么强,为什么我们之前没怎么听说过呢?为什么不用它代替vector和list呢?

其实只看头插头删,尾插尾删这些,deque还是可以的,但论综合性,是无法代替vector和list的

这是100w数据量下,第一个deque是将数据拷贝到vector里,然后进行排序,再拷贝回来,第二个是直接对deque进行排序,差距是很明显的 

deque其实是一个很一般的设计,这也想要,那也想要,最后啥也干不了

vector直接下标随机访问,但是扩容,中间以为头部的插入删除效率不高,list则是支持任意位置插入删除,但不支持下标随机访问,是按需要求申请释放的

vector是连续的,list的一个一个的节点,所以就有人想,我们开辟一段一段的空间,然后用一个中控数组指向他们(这个中控数组不是从头开始指向的), 中控数组就是一个指针数组,这样一看感觉和vector<vector<int>>差不多,但其实差距是很大的,vector指向的vector是可以扩容的,每一段的大小都可以不一样,但是中控数组指向的空间是一样大的

指针数组满了,要进行扩容,不过扩容的代价是非常底的,它指向的空间是不用动的,只需拷贝指针,不需要拷贝原数据,我们上面说,中控数组是从中间开始指向的,所以头插时,是可以从前面的空间插入的

就像这样,而且它的插入是倒着往前走的 ,先插入的-1,再插入的-2

相比vector,它缓解了扩容的问题,头插头删问题,但是 [ ] 不够极致,[ ] 要去计算在哪个buff(指向的数组)的第几个

 它要先计算在不在第一个buff,第一个buff不是满的,不是从零开始的,第一个buff是倒着往前走的,如果在的话就找位置访问,如果不在,i -=第一个buff数组的size,第几个buff = i / buff,在这个buff的第几个 = i % buffsize

所以是不够极致的,vector是直接指针解引用访问就行了,而这里是需要计算的,这也是为什么上面效率比对时差距明显的原因了,在大量访问时,这个计算是很麻烦的

相比list,它支持下标随机访问,cpu高速缓存效率不错,头尾插入删除不错,但是中间的插入删除就不好了,中间插入的话一个是挪动数据,这个效率太低,另一个就是扩容,扩容中间这个buff,是不好的,库里面也没有这么做,这样做会影响上面的计算,会使[ ]的效率进一步下降

只有高频的头尾的插入删除,才适合deque,所以deque就用来适配栈和队列的默认容器

deque的底层是非常复杂的

底层是一段假象的连续空间,实际是分段连续的,为了维护其 整体连续 以及随机访问的假象,落 在了 deque 的迭代器身上 因此 deque 的迭代器设计就比较复杂,迭代器就封装了四个指针,如下图所示

 我们再看一张图

start就相当于begin,finish就相当于end,要进行一遍迭代器遍历,把begin给it,然后解引用,解引用返回*cur 

这是源码 ,我们再看看++,有两个操作,first和last是指向这段数组的起始和末尾,cur!=last 说明没有走完,++cur即可,如果走完了,迭代器要指向下一个buff,里面的node就要++,指向下一个buff的地址,再解引用就可以指向下一个buff,然后再让first和last指向这个buff,cur再指向这个buff,我们再看什么适合结束,当it和end在同一个buff时,cur还没走完,当cur等于end的cur时就结束

 

 

 

 deque我们稍微看看就行

优先级队列

 优先级队列和deque一样,都不能算队列,因为队列是先进先出的,这两个只是占了个名

我们看到它也是容器适配器,默认使用了vector

这些接口也都很简单

 优先级队列底层就是二叉树的堆,默认大的优先级高

默认是大堆,我们仔细看默认容器适配器传的是小于(第三个模板参数),但默认又是大堆 ,是反过来的

传一个greater才能变为小堆 ,而默认的less是大堆,是相反的,大家要牢记

 不一样的地方是构造函数给了迭代器区间构造,可以给一个区间,用这些值构造堆

这里还可以换成deque,不过是不太好的,因为deque的随机访问要计算的,不如vector

 模拟实现

下面我们模拟实现一下优先级队列

namespace bai {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 + 1] > _con[child]){++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);}}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];}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};
}

这里逻辑其实都不难,用到了以前我们堆的知识,如果对堆还不够理解的同学可以看我之前对堆的详解

(20条消息) 堆及其多种接口与堆排序的实现_KLZUQ的博客-CSDN博客

我们测试一下,没有问题 

仿函数

仿函数,也叫做函数对象

如果不看其他的,只看cout输出这里,我们会认为lessfunc是一个函数

实际上这个类重载了括号,本质等价于lessfunc.operator()

本质是把这个类的对象像函数一样使用,作用是替代C语言里的函数指针

 我们将它变为类模板,使用范围就变的很广阔了 

那仿函数到底是干什么的呢?

像我们这样的比较,是写死的 ,我们可以用函数指针来让它变得灵活,但C++不使用函数指针,因为函数指针的可读性很差,所以就开发出了仿函数

 

我们把这些先全改为<的比较,然后使用com来进行控制 

这样我们的比较就变得灵活了,传入less或者greater,就可以切换大于和小于的比较,从而调整我们是要建大堆,还是建小堆 ,我们可以根据需求写出自己的less和greater,对各种类型都可以进行比较

简单来说,仿函数就是通过模板参数,调用比较类型,调用operator(),operator()是我们自己实现的,或者我们用库里面的less或者greater也可以

 我们把之前写过的Date类拿过来,都进行排序

有些场景下我们需要自己写仿函数

 

这个情况非常诡异,每一次运行结果都不一样 

因为我们的类型是一个指针,new出来的地址大小是不确定的,带有随机性,所以会有这种结果

 我们写一个这样的仿函数,就可以解决问题

我们可以通过仿函数来控制比较规则,而不是被优先级队列给写死了

全部代码

//stack.h
using namespace std;
#include<iostream>
#include<list>
#include<vector>namespace bai
{//容器适配器template<class T,class Container = vector<T>>class stack{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}T& top(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};void test() {stack<int> st1;st1.push(1);st1.push(2);st1.push(3);st1.push(4);while (! st1.empty()) {cout << st1.top() << " ";st1.pop();}cout << endl;stack<int, list<int>> st2;st2.push(10);st2.push(20);st2.push(30);st2.push(40);while (!st2.empty()) {cout << st2.top() << " ";st2.pop();}cout << endl;}
}
//queue.h
#include<iostream>
#include<list>
#include<vector>
using namespace std;
namespace bai
{//容器适配器template<class T, class Container = list<T>>class queue{public:void push(const T& x){_con.push_back(x);}void pop(){//_con.pop_front();_con.erase(_con.begin());}T& front(){return _con.front();}T& back(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};void test_queue() {//std::queue<int,vector<int>> q;queue<int, list<int>> q2;q2.push(10);q2.push(20);q2.push(30);q2.push(40);while (!q2.empty()) {cout << q2.front() << " ";q2.pop();}cout << endl;}
}
//priority_queue.h
using namespace std;
#include<vector>
#include<iostream>
namespace bai {template<class T,class Container = vector<T>,class Comapre = less<T>>class priority_queue{private:void AdjustDown(int parent){Comapre com;size_t 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){Comapre 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:priority_queue(){}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);}}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];}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};class Date{public: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);}friend ostream& operator<<(ostream& _cout, const Date& d);private:int _year;int _month;int _day;};ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "-" << d._month << "-" << d._day;return _cout;}struct LessPDate{bool operator()(const Date* p1, const Date* p2){return *p1 < *p2;}};void test() {priority_queue<Date*, vector<Date*>,LessPDate> pq;pq.push(new Date(2021, 7, 28));pq.push(new Date(2021, 6, 28));pq.push(new Date(2021, 8, 28));while (!pq.empty()) {cout << *pq.top() << " ";pq.pop();}cout << endl;}
}

以上即为本期全部内容,希望大家可以有所收获

如有错误,还请指正

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

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

相关文章

【C++初阶】模板

⭐博客主页&#xff1a;️CS semi主页 ⭐欢迎关注&#xff1a;点赞收藏留言 ⭐系列专栏&#xff1a;C初阶 ⭐代码仓库&#xff1a;C初阶 家人们更新不易&#xff0c;你们的点赞和关注对我而言十分重要&#xff0c;友友们麻烦多多点赞&#xff0b;关注&#xff0c;你们的支持是我…

亚马逊云科技纽约峰会,充分释放数据价值和生成式AI的潜力

生成式AI将深刻改变每个公司的运营方式&#xff0c;标志着人工智能技术发展的新转折点。亚马逊云科技昨日在纽约峰会上宣布&#xff0c;推出七项生成式AI新功能&#xff0c;进一步降低了生成式AI的使用门槛&#xff0c;让无论是业务用户还是开发者都能从中受益。借助这些新功能…

【解析excel】利用easyexcel解析excel

【解析excel】利用easyexcel解析excel POM监听类工具类测试类部分测试结果备注其他 EasyExcel Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存&#xff0c;poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题&…

2、基于redis实现分布式锁

目录 2.1. 基本实现2.2. 防死锁2.3. 防误删2.4. redis中的lua脚本2.4.1 redis 并不能保证2.4.2 lua介绍 2.5. 使用lua保证删除原子性 2.1. 基本实现 借助于redis中的命令setnx(key, value)&#xff0c;key不存在就新增&#xff0c;存在就什么都不做。同时有多个客户端发送setn…

亿信华辰举行制造业数字化转型研讨会,解密数字化最佳实践

制造业是国家经济命脉所系&#xff0c;推进制造业数字化转型已成为发展数字经济的重中之重。今天&#xff08;5月9日&#xff09;上午&#xff0c;亿信华辰携手沙丘社区成功举办“制造业数字化转型研讨会”&#xff0c;1.3万人线上观看&#xff0c;汇聚华为、鼎捷软件、亿信华辰…

uniapp实现预约时间选择弹窗组件

做了个组件&#xff0c;实现出当日预约时间组件&#xff0c;效果图如下 废话不多说&#xff0c;直接上代码&#xff0c;代码简单&#xff0c;参数自己任意改 <template><view class"inventory"><u-popup :show"show" :round"10"…

OpenAI重磅官宣ChatGPT安卓版本周发布,现已开启下载预约,附详细预约教程

7月22号&#xff0c;OpenAI 突然宣布&#xff0c;安卓版 ChatGPT 将在下周发布&#xff01;换句话说&#xff0c;本周安卓版 ChatGPT正式上线&#xff01; 最早&#xff0c;ChatGPT仅有网页版。 今年5月&#xff0c;iOS版ChatGPT正式发布&#xff0c;当时OpenAI表示Android版将…

【C++ 重要知识点总结】自定义类型-类和结构体

类 类的基本特性 数据抽象和封装继承多态 1 类的构成——抽象 概念 数据抽象是一种依赖于接口和实现的分离的编程技术。类的接口包括用户所能执行的操作&#xff1b;类的实现包括类的数据成员、负责接口实现的函数体以及定义类所需要的的各种私有函数。封装实现了类的接口和实…

Java使用FFmpeg实现mp4转m3u8

Java使用FFmpeg实现mp4转m3u8 前言FFmpegM3U8 一、需求及思路分析二、安装FFmpeg1.windows下安装FFmpeg2.linux下安装FFmpegUbuntuCentOS 三、代码实现1.引入依赖2.修改配置文件3.工具类4.Controlle调用5.Url转换MultipartFile的工具类 四、播放测试1.html2.nginx配置3.效果展示…

Web3.0:已经开启的互联网革命!

1 痛点 2 web发展形态 只读、封闭式、协作式。 3 一个高度联系、全球统一的数字经济体 去中心化架构通过计算几余打破数据垄断&#xff0c;同时实现数字确权大量的功能依靠智能合约自动实现&#xff0c;运转效率大大提升DAO大量涌现&#xff0c;全球范围实现资源配置 4 特…

【Element-ui】学习与使用

网站快速成型工具Element&#xff0c;一套为开发者、设计师和产品经理准备的基于vue2.0的桌面端组件库 安装 npm i element-ui -S 在项目中安装element-ui&#xff0c;安装了以后查看package.json中的依赖中有没有element-ui的版本&#xff0c;如果有&#xff0c;则说明安装成功…

Spring Boot 自定义启动画面

文章目录 自定 Banner获取属性设置颜色实操关闭 Banner参考 我们启动项目的之后&#xff0c;会在控制台上看到类似下面的画面&#xff1a; 那么&#xff0c;我们是否可以自定义呢&#xff1f; 肯定可以 自定 Banner 上面的截图信息就是 Banner 信息&#xff0c;我们可以在项目…

在Chrome(谷歌浏览器)中安装Vue.js devtools开发者工具及解决Vue.js not detected报错

文章目录 一、Vue.js devtools开发者工具安装1.打开谷歌浏览器——点击扩展程序——选择管理扩展程序2.先下载添加一个谷歌助手到扩展程序中&#xff08;根据提示进行永久激活&#xff09;3.点击谷歌浏览器的应用商店4.输入Vue.js devtools——搜索——选择下载 二、解决Vue.js…

如何为WordPress博客网站配置免费域名HTTPS证书

文章目录 如何为WordPress博客网站配置免费域名HTTPS证书前置条件&#xff1a;步骤1 申请免费的域名HTTPS证书步骤2 将HTTP证书配置到cpolar的配置文件中2.1 创建证书文件夹2.2 修改cpolar配置文件2.3 重启cpolar服务2.4 查看后台Wordpress隧道是否在线正常2.5 用浏览器打开站点…

MyBatis基础模块-类型转换模块

文章目录 1. 为什么需要类型转换模块2. TypeHandler 1. 为什么需要类型转换模块 执行sql&#xff0c;在PreparedStatement设置参数时&#xff0c;需要把java类型转换成jdbc类型&#xff0c;而从结果集中获取数据时&#xff0c;需要把jdbc类型转换为java类型。 2. TypeHandle…

Linux操作系统~必考面试题⑧

1、pwd 命令 pwd 命令用于查看当前工作目录路径。 实例&#xff1a; 查看当前路径 pwd 查看软链接的实际路径 pwd -P 2、rmdir 命令 从一个目录中删除一个或多个子目录项&#xff0c;删除某目录时也必须具有对其父目录的写权限。 注意&#xff1a;不能删除非空目录实例&…

Linux系统MySQL中用户的权限管理

本节主要学习用户权限管理的概述&#xff0c;用户权限类型&#xff0c;用户赋权&#xff0c;权限删除&#xff0c;用户删除等。 目录 一、概述 二、用户权限类型 三、用户赋权 四、权限删除 五、用户删除 一、概述 数据库用户权限管理是数据库系统中非常重要的一个方面&am…

统一观测丨使用 Prometheus 监控 Cassandra 数据库最佳实践

作者&#xff1a;元格 本篇内容主要包括四部分&#xff1a;Cassandra 概览介绍、常见关键指标解读、常见告警规则解读、如何通过 Prometheus 建立相应监控体系。 Cassandra 简介 Cassandra 是什么&#xff1f; Apache Cassandra 是一个开源、分布式、去中心化、弹性可伸缩、…

Vue3封装函数组件(ElImageViewer)预览图片

目录结构 index.vue <template><el-image-viewer v-if"show" v-bind"$attrs" hide-on-click-modal close"show false" /> </template><script setup> import { ref, watch } from "vue" import { ElImageV…

Linux基础以及常用命令

目录 1 Linux简介1.1 不同应用领域的主流操作系统1.2 Linux系统版本1.3 Linux安装1.3.1 安装VMWare1.3.2 安装CentOS镜像1.3.3 网卡设置1.3.4 安装SSH连接工具1.3.5 Linux和Windows目录结构对比 2 Linux常用命令2.0 常用命令&#xff08;ls&#xff0c;pwd&#xff0c;cd&#…