C++ - stack 和 queue 模拟实现 -认识 deque 容器

news2024/11/24 8:59:06

stack模拟实现

 用模版实现 链式栈 和 顺序栈

 对于stack 的实现,有两种方式,一种是连续空间存储的顺序栈,一种是不连续空间存储的链式栈,在C当中如果要使用两种不同的栈的话,实现方式是不一样的,他们的底层逻辑是不一样的 。所以要用不同的方式去实现。

而在C++当中有模版,而且C++当中的 stack 和 queue 不是单独实现的,是复用了 deque 这个类,也就意味着,在同一类当中 ,可以用模版参数来实现不同的栈。如下所示:

namespace My_stack
{
	template<class T, class Container>
	class stack
	{
	public:

	private:
		Container _con;
	};
}

由上述代码,我们就可以在外面自定义这个栈是 链式结构 还是 顺序结构,如下代码所示:

	My_stack::stack<int, vector<int>> st1;
	My_stack::stack<int, list<int>> st2;

 我们这里的顺序栈 使用的不是 deque 容器,而是vector,其实deque更好,这样只是简单实现,所以用vector 就足够。

 上述就是使用上述 stack 模版的方式,这样,我们就可以自定义 stack 的 存储类型,和存储结构了

直接复用有一个好处,我们可以不用实现构造函数 和 析构函数,因为我们使用的空间不是我们是实现的 stack 类监管,而是由 vector  和 list 监管。

 在官方文档中,我们发现这里的 Container 模版参数给了缺省值,其实模版参数也是有缺省值的,这里的缺省值给的是类型。如下所示:

 所以我, 还可以进行优化:
 

	template<class T, class Container = vector<T>>

 增删查改

 因为stack 是 vector 和 list 容器来实现,所以下面的函数也都可以复用 两个容器当中函数。

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

 总代码

#pragma once

namespace My_stack
{
	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;
	};
}

queue 模拟实现

 队列的实现 和  栈是一样的,都可以是使用其他类的 复用,达到容器适配器的效果:

#pragma once
#include<vector>
#include<list>
#include<deque>

namespace bit
{
	// 
	template<class T, class Container = deque<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()
	{
		 queue<int, list<int>> q;

		//queue<int, vector<int>> q;
		q.push(1);
		q.push(2);
		q.push(3);
		q.push(4);

		while (!q.empty())
		{
			cout << q.front() << " ";
			q.pop();
		}
		cout << endl;
	}
}

注意:虽然我们上述实现的了 queue的 vector底层结构实现,但是queue用 vector 不太好,因为缺如是 先进先出的结构,这里vector 找尾需要遍历,存在效率问题,而且头删存储移位问题,效率不好。

在库当中的 queue 就没有支持 vector 为底层结构的 queue,因为在库当中,queue的Pop函数是直接调用的 pop_front() 这个函数,而这个函数是 vector没有的:

 deque

 deque的优劣

 我们前面多次提到了 deque 这个容器,deque 也叫做 双端队列,虽然叫做队列,但是实际上这个容器已经不是队列了,队列要求是要先进先出,但是deque当中实现了其他功能,如下所示:

 我们发现,deque实现了 vector 有 list 没有的 operator[] 函数,还实现了 vector 没有 list 有的 头插头删等等。可以说,deque的功能 是 vector 和 list 的结合体。

虽然功能上很齐全,但是单个功能的效率不如 vector 和 list。

 下述测试 deque 直接 sort()快排,和 deque 拷贝数据到 vector 之后排序,在拷贝回去,这两种方式的时间消耗(10000000个数据):

void test_op()
{
	srand(time(0));
	const int N = 1000000;
	vector<int> v1;
	vector<int> v2;
	v1.reserve(N);
	v2.reserve(N);

	deque<int> dq1;
	deque<int> dq2;


	for (int i = 0; i < N; ++i)
	{
		auto e = rand();
		//v1.push_back(e);
		//v2.push_back(e);
		dq1.push_back(e);
		dq2.push_back(e);
	}

	// 拷贝到vector排序,排完以后再拷贝回来
	int begin1 = clock();
	// 先拷贝到vector
	for (auto e : dq1)
	{
		v1.push_back(e);
	}

	// 排序
	sort(v1.begin(), v1.end());

	// 拷贝回去
	size_t i = 0;
	for (auto& e : dq1)
	{
		e = v1[i++];
	}

	int end1 = clock();

	int begin2 = clock();
	//sort(v2.begin(), v2.end());
	sort(dq2.begin(), dq2.end());

	int end2 = clock();

	printf("deque copy vector sort:%d\n", end1 - begin1);
	printf("deque sort:%d\n", end2 - begin2);
}

输出:

 发现 deque 效率更低,但是deque的 拷贝消耗不大,几乎可以省略。

 deque的底层实现

 之前的vector 容器,扩容的时候都是有代价的,比如 vector 容器,它的扩容就是 新开辟一个空间,然后把原空间当中的数据拷贝到新的空间当中。这样做的代价不小,所以deque就在list 和 vector 之间做了新的结构设计:

 deque底层设计:

  •  如上图所示,是deque的结构设计,它有一个中控(指针数组)来维护多个 顺序表(连续存储结构)。而且存储各个 顺序表的头指针的 中控(指针数组)并不是从开始来存储指针,而是从中间开始存储,这样方便头插和尾差的时候,往前或后新开辟空间,用新指针来维护新开皮带空间。
  • 每一个顺序表的size(),元素个数是一样的。
  • 如果像头插和尾插数据,这种操作,可以新开辟一块空间,用新的指针来维护,在中控(指针数组当中指针是有顺序的)。
  • 如果中控(指针数组)存储的指针满了,可以对 中控(指针数组)单独扩容。
  • 上述提到了 中控(指针数组)可以单独扩容,这样的好处是:扩容时候,不需要拷贝其他顺序表当中的数据,主需要拷贝自己的指针数据,或者是存储在这单个的顺序表当中数据即可,大大减少了靠北的消耗。

 这么一说,上述 deque 的结构,跟 vector<vector<T>> 这个结构差异还是很大的,vector<vectorT>> 当中的 “指针数组 ” 存储的是 一个一个的对象,而每一个对象当中都有一个 数组。

deque当中的 头插和尾插顺序

问题那么 中间插入是不是,不需要后面挪动数据,直接在想插入的位置的顺序表空间当中直接插入,需要挪动的只是这个小的顺序表中后面的数据,如果满了,只用扩容这个一个小的顺序表空间。

 其实不是,可以看下面 vector 当中的对比operator[] 的实现方式

 迭代器

 deque的底层实现是非常复杂的,处了上述的基本结构和功能,deque的迭代器的实现也是很复杂,deque的迭代器的封装了 四个指针:

  • first 和 last 分别指向 每一段 数组的 开始和结束位置。
  • cur 指向 在这个数组中,当前数据的位置。
  • node 是一个二级指针,反向过来指向中控器,这样利于寻找下一个数组。

 迭代器遍历过程如下图:
 

  •  *it :解引用 it, 解引用的是 cur,由cur 来遍历数组。
  • ++it:有两种情况,一种是没有走到 last ,此时说明当前数组还没有走完,那么就直接 ++cut;如果当前数组走完了,就 ++node ,这样 node 就指向了下一个数组。更新其他是哪个指针的指向下一个位置的指定位置。

 库当中是这样实现的:

 

 deque 和 vector 相比

 相比于 vector, deque 很大程度上的解决了扩容的问题(头插头删,中间插/删)。但是 在 operator[] 这个函数的实现比较复杂,这个函数需要计算,这个下标在哪一个 顺序表当中,计算方式如下

  • 首先看是否在第一个 顺序表当中,在就找位置访问;
  • 不在第一个数组,用 i - 第一个数组的 size(),再去取 / 计算在第几个数组当中,在取 % 计算在这个数组的那一个位置。

虽然看上去计算并不多,但是在大量访问的时候还是有很大的效率问题,这里就印证了为什么上述deque 的sort() 跑不过 vector 的sort(),因为 在sort()当中用了很多的 operator[] 函数。

deque 和 list 相比 

 好处:

  •  首先是支持 operator[] 函数。
  • cpu高速缓存当中效率不错。

 缺点:

 头尾插删,都差不多,但是在中间插入和删除不好。这里没有使用上述在 deque 的底层实现小结当中最后提出的问题一样,没有使用那样的方式来实现,因为如果每一个顺序表的大小都是相等的,那么 operator[] 函数在计算下标位置的时候更加方便快捷。如果每一个顺序表的大小不一样,只能使用遍历的方式来计算个数,这样很麻烦。

所以,deque采用但是 挪动数据来实现的 中间插入和删除。那么挪动数据所带来的代价就很大了。

deque 的 使用场景

  由上述分析,我们发现deque适合 高频的头尾插删,不是和 中间插删。

对于 高频的头尾插删,比如:上述的 stack 和 queue 容器就非常适合。

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

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

相关文章

新型双功能整合剂2374782-03-1,NOTA-FAPI-04,具有良好的配位和整合能力

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ NOTA-FAPI-04&#xff0c;大环化合物-FAPI-04 PART1----产品结构式 PART2----产品规格 1.CAS号&#xff1a;2374782-03-1 2.分子式&#xff1a;C36H47F2N9O8 3.分子量&#xff1a;771.8238 4.沸点 1061.865.0 C(Predicted)…

Unity自定义后处理——Tonemapping色调映射

大家好&#xff0c;我是阿赵。   继续介绍屏幕后处理&#xff0c;这一期介绍一下Tonemapping色调映射 一、Tone Mapping的介绍 Tone Mapping色调映射&#xff0c;是一种颜色的映射关系处理&#xff0c;简单一点说&#xff0c;一般是从原始色调&#xff08;通常是高动态范围&…

Langchain 集成 Milvus

Langchain 集成 Milvus 1. 安装 Docker2. 部署 Milvus3.4. Langchain 集成 Milvus 1. 安装 Docker refer: https://docs.docker.com/engine/install/centos/ Milvus 会以容器方式启动&#xff0c;所以先安装 Docker。(本示例使用的是 Alma Linux 9.2) 卸载旧版本&#xff0c…

文件上传--题目

之前有在技能树中学过文件上传&#xff0c;正好借这次进行一个整合&#xff1a; 技能树中所包含的题目类型有 无限制绕过 1.上传一句话木马 2.链接中国蚁剑 前端验证 1.会发现这个网站不让提交php&#xff0c;改后缀为jpg格式&#xff0c;再用burp抓包 2.在用中国蚁剑连接 .…

Python Web 开发及 Django 总结

title: Python Web 开发及 Django 总结 date: 2023-07-24 17:26:26 tags: PythonWeb categories:Python cover: https://cover.png feature: false Python 基础部分见&#xff1a;Python 基础总结 1. 创建项目 1.1 命令行 1、下载安装 Django 在终端输入 pip install djan…

SpringBoot——内置数据源

简单介绍&#xff1a; 在之前我们介绍SpringBoot的数据层解决方案的时候&#xff0c;曾说到过在数据层是由数据源&#xff0c;持久化技术和数据库组成的&#xff0c;之前我们一直使用的都是DruidMyBatisMySQL组合的解决方案。这三种方案在之前我们都介绍过如何整合以及基础的使…

Django模型将模型注释同步到数据库

1、安装django-comment-migrate库 pip install django-comment-migrate 2、将库注册到settings.py文件中 INSTALLED_APPS [...django_comment_migrate, # 表注释... ] 3、加注释 3.1、给模型&#xff08;表&#xff09;加注释 在模型的class Meta中编辑 verbose_name&…

嵌入式:QT Day2

一、继续完善登录框&#xff0c;当登陆成功时&#xff0c;关闭登陆页面&#xff0c;跳转到新的界面中 源码&#xff1a; widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QDebug> //用于打印输出 #include <QIcon> …

部署 cacti 监控系统

Cacti Cacti&#xff08;流量和性能监测为主&#xff09; Cacti 在英文中的意思是仙人掌的意思&#xff0c;Cacti 是一套基于 PHP、MySQL、SNMP 及 RRDTool 开发的网络流量监测图形分析工具。它通过 snmpget 来获取数据&#xff0c;使用 RRDtool 绘画图形&#xff0c;而且你完…

LiveGBS流媒体平台GB/T28181常见问题-国标设备列表没有数据海康大华宇视华为监控摄像机NVR硬件设备注册不上来如何排查?

LiveGBS中国标设备列表没有数据海康大华宇视华为监控摄像机NVR硬件设备注册不上来如何排查&#xff1f; 1、国标设备列表看不到注册上来的设备2、检查方式2.1、检查设备注册信息2.2、检查服务器防火墙 3、尝试配置免密接入3.1、基础配置->白名单3.2、添加白名单 4、更多排查…

C# 目标平台为x64,自定义控件不可用,显示控件未能加载,错误解决方法

由于项目加载第三方的dll需要编译成x64&#xff0c;设置编译目标为x64 结果打开窗口设计器时&#xff0c;自定义的控件不能显示及加载 错误消息&#xff1a;未能找到类型“XXX”。请确保已引用包含此类型的程序集。如果此类型为开发项目的一部分&#xff0c;请确保已使用针对当…

ChatGPT有几个版本,哪个版本最强,如何选择适合自己的?

​ChatGPT就像内容生产界的瑞士军刀。它可以是数学导师、治疗师、职业顾问、编程助手&#xff0c;甚至是旅行指南。只要你知道如何让它做你想做的事&#xff0c;ChatGPT几乎可以提供你要的任何东西。 但重要的是&#xff0c;你知道哪个版本的ChatGPT最能满足你的需求吗&#x…

STM32CubeIDE(I2C)

目录 一、IIC轮询模式 1.1 配置 1.2 编写AHT20驱动 1.2.1 aht20.h 1.2.2 aht20.c 二、I2C中断 2.1 打开中断 2.2 分离读取流程 2.3 在主函数中重新编写读取流程 2.4 在i2c.c中重新定义stm32f1xx_hal_i2c.h中的两个函数 三、I2CDMA 3.1 配置DMA通道 3.2 代码的修改 一…

【优选算法题练习】day9

文章目录 一、DP35 【模板】二维前缀和1.题目简介2.解题思路3.代码4.运行结果 二、面试题 01.01. 判定字符是否唯一1.题目简介2.解题思路3.代码4.运行结果 三、724. 寻找数组的中心下标1.题目简介2.解题思路3.代码4.运行结果 总结 一、DP35 【模板】二维前缀和 1.题目简介 DP…

LeetCode三步问题(动态规划)

LeetCode三步问题&#xff08;动态规划&#xff09; 编写代码代码优化 链接: 三步问题 编写代码 class Solution { public:int waysToStep(int n) {if(n 1 || n 2) return n;vector<int> dp(n1);const int MOD 1e9 7;dp[0] dp[1] 1;dp[2] 2;for(int i 3;i<n…

WEB:file_include

背景知识 php伪协议 文件包含漏洞 php包含漏洞函数 题目 由题目可知这个是文件包含的题目&#xff0c;先用常用的协议先查看一下 payload ?filenamephp://filter/readconvert.base64-encode/resourceflag.php 出现了 发现filter&#xff0c;base64被过滤了 尝试其他协议 …

Elasticsearch监控工具Cerebro安装

Elasticsearch监控工具Cerebro安装 1、在windwos下的安装 1.1 下载安装包 https://github.com/lmenezes/cerebro/releases/download/v0.9.4/cerebro-0.9.4.zip 1.2 解压 1.3 修改配置文件 如果需要修改相关信息&#xff0c;编辑C:\zsxsoftware\cerebro-0.9.4\conf\applica…

c语言用冒泡排序模拟实现qsort排序

1、简单介绍冒泡排序 冒泡排序就是两两相邻元素进行比较&#xff0c;如果不满足顺序就进行交换。现有一组整数&#xff0c;将其用冒泡排序实现排序为升序。 假设有这样一组整数&#xff1a;9 8 7 6 5 由此可知&#xff0c;如果一个整型数组有num个元素&#xff0c;则需走num…

第一次作业 运维高级 MySQL备份与还原

1.创建student和score表 CREATE TABLE student ( id INT(10) NOT NULL UNIQUE PRIMARY KEY , name VARCHAR(20) NOT NULL , sex VARCHAR(4) , birth YEAR, department VARCHAR(20) , address VARCHAR(50) );CREATE TABLE score ( id INT(10) NOT NULL UNIQUE PRIMARY KEY AUTO…

交叉编译----宿主机x86 ubuntu 64位-目标机ARMv8 aarch64

1.交叉编译是什么&#xff0c;为什么要交叉编译 编译&#xff1a;在一个平台上生成在该平台上的可执行代码交叉编译&#xff1a;在一个平台上生成在另一个平台上的可执行代码交叉编译的例子&#xff1a;如51单片机的可执行代码&#xff08;hex文件&#xff09;是在集成环境kei…