栈和队列的学习以及实现+双端队列的底层原理

ops/2024/12/22 20:10:08/

  在本次的博客当中我们来进行讲解栈和队列相关的内容。首先要认识一下栈和队列的使用方法。

   栈和队列的使用

  我们可以看到相比于我们之前使用的list,vector还有string类来说栈和队列就简单了很多,没有太多复杂的接口。

  因为对于栈和队列来说输入和输出都有限制,由于我们只能在特定的位置进行插入数据以及输出数据,那么就代表我们不能拥有其他的数据插入接口和数据输出接口以及数据打印接口。这就造成了我们的栈和队列不需要有迭代器等复杂的结构的特点。对于这么简单的数据结构我们就不进行过多的介绍了,直接在实现其底层逻辑的时候顺便了解其使用的方法。

  stack类的实现

  

  首先观察stack的构造函数可以发现:我们有了一个新的内容container。这个变量实际上是我们的模板参数所添加的。

  我们的stack的模板参数一共有两个,第一个模板参数是stack栈当中存放的数据类型,第二个模板参数是我们的container容器。也就是使用什么样的容器实现我们的栈,因为实现栈的方式有很多种,想象一下我们既可以使用vector实现一个顺序栈也可以使用list实现一个链式栈。

  

  我们需要创建一个container的成员变量,我们这个成员变量可以进行实例化成为各种类型,例如vector<int>或者list<int>等。对于stack当中的函数功能我们直接调用我们container所对应的容器当中已经实现的内置函数即可。需要注意的是:我们在返回栈顶元素的时候不能够使用 [ ] 直接找到链表当中最后一个元素的位置,因为我们的list容器并不支持该功能,如果使用 [ ] list容器就无法进行适配操作了。

  代码如上图所示,我们来进行测试我们的代码是否可以正常运行。

  代码运行一切正常。

   queue类的实现

  对于队列的实现逻辑上跟我们的栈很相似,我们只需要对插入数据以及删除数据等接口进行简单的修改即可。

  首先我们需要修改返回某位置元素的函数,将top函数修改成为front函数。修改我们删除数据的位置,需要从队列的头部进行删除数据。其他函数照旧。程序运行效果如下:

  代码运行一切正常。

  deque函数的介绍

  但是相信大家肯定会有一个疑惑:我记得我们在使用stack栈的时候不需要传两个参数呀?我们只需要传入一个参数不久可以了吗?我们仔细观察一下stack和queue的模板参数就会发现:在系统库实现的过程当中我们设置了一个默认的模板参数deque。

  这个默认参数的实现就使得我们不需要手动传入参数就可以正常使用stack。我们同样可以设置一个默认的模板参数。

  难道这个默认的模板参数只能选择deque吗?我们选择其他的数据容器行不行呢?我们测试一下会发现是完全可以的。

  对于我们的代码即使使用的是list容器作为默认参数也完全可以胜任stack的所有功能的。那么我们为什么要选择deque作为默认的模板参数呢?

  我们来仔细阅读以下deque的官方文档。

  我们会发现一个很神奇的现象,那就是我们的deque容器很神奇,就好像是vector和list的融合体一样,既可以使用 [ ] 进行访问,又可以使用push_front函数进行头插操作。就好像没有限制一样。难道就没有效率上的负担吗?

  我们会发现deque类是使用一个支持从两侧插入数据以及从两侧输出的queue实现的。其真实的底层逻辑结构实际上是一个分段式的小型数组。

  结构类似于上图。我们通过一个指针数组将这些小型的数组链接起来构成了一个新的容器也就是deque。

  这样有什么优势呢?我们会发现这样的结构让我们兼备了vector数组以及链表的优点,如果我们想头插数据的时候就可以直接在最前面创建一个数组,将指针保存进入我们的指针数组当中即可,不需要我们重新移动数据。尾插操作相同。

  并且由于我们同样采用数组的形式也可以很快的计算出我们某个元素所在的位置。相比于我们的list链表也支持了随机访问 [ ]。

  但是deque真的有这么方便吗?其实不然,我们仔细分析以下就会发现:即使deque支持了 [ ] 随机访问效率也很低,我们需要进行大量的计算,首先我们需要减去第一个数组当中的元素,因为我们进行头插操作的数组存储数据可能不满,之后再使用剩下的数据对小数组进行除法,求出我们元素应该位于哪一个数组当中,最后求余找到我们数据的具体的位置。相对于我们的vector容器来说 [ ] 的执行效率大大降低。

  相比于list虽然头插跟头删,尾插尾删的效率很高,但是对于其他位置的数据呢?对于其他位置的数据的插入和删除操作来说,如果我们同样重新申请了一个数组数组当中的数据存储的位置就进行了偏差,那么我们的 [ ] 操作的效率就会有更大的降低,因为我们计算数据的位置的时候使用了除法取余操作,默认的就是中间的数据都是连续进行存储的。所以对于中间数据的插入操作来说同样需要花费大量的时间进行移动修改操作。

  深究底层逻辑我们会发现,deque在使用的时候很方便,但是做不到完全的替代vector或者list进行使用,其特有的工作形式并不适合我们日常的数据存储形式。但是这种存储形式却适合我们实现双端队列,因为如果我们仅仅进行头插头删尾插尾删操作效率就会很高。

  作为双端队列的容器,相对于vector来说支持头插头删操作,相对于list来说我们不需要插入一个数据就进行扩容一次,因为我们每一次开辟都会开辟一个小数组,插入到头部之后当数组满的时候才会进行下一次的开辟。还有就是CPU的访存效率也会得到提高,因为CPU在读取数据的时候会直接将连续的一段数据读取到内存当中,减少访存次数得到效率的提升。

  综上所述我们的双端队列作为我们需要进行多次头尾操作的底层容器来说再适合不过了,这也就是为什么在系统库当中stack和queue的默认模板参数式deque的原因。


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

相关文章

Python爬虫所需的技术及其原理(简单易懂)

导言 随着互联网的发展&#xff0c;大量的数据被存储在网络上&#xff0c;而我们需要从中获取有用的信息。Python作为一种功能强大且易于学习的编程语言&#xff0c;被广泛用于网络爬虫的开发。本文将详细介绍Python爬虫所需的技术及其原理&#xff0c;并提供相关的代码案例。…

django之ForeignKey、OneToOneField 和 ManyToManyField

在Django中&#xff0c;ForeignKey、OneToOneField 和 ManyToManyField 是用于定义模型之间关系的字段类型。 ForeignKey ForeignKey 用于定义多对一的关系。例如&#xff0c;一个Employee可以属于一个Department&#xff0c;一个Department可以有多个Employee。 from djang…

【ragflow】安装2:源码安装依赖

中文文档【ragflow】安装1: docker:失败官方说的成功 docker 安装的启动失败 重新来一遍,不会重新拉取: root@k8s-master-pfsrv:/home/zhangbin/perfwork/rag# cd ragflow/ root@k8s-master-pfsrv:/home/

揭开容器的面纱:容器技术全景概述

随着云计算的快速发展&#xff0c;容器技术已经成为IT行业的重要组成部分。Docker作为一种领先的容器化技术&#xff0c;为应用程序的开发、部署和运行带来了革命性的变化。本篇文章将详细介绍容器技术的概念、发展历程及其在现代计算中的应用。通过对Docker的深入了解&#xf…

docker实战基础一 (Docker基础命令)

一、docker安装 # step 1: 安装必要的一些系统工具 yum install -y yum-utils device-mapper-persistent-data lvm2 # Step 2: 添加软件源信息 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # Step 3: 更新并安装 Doc…

深度学习项目实践——QQ聊天机器人(transformer)(三)功能实现的方法——NoneBot2插件结构与编写

深度学习项目实践——QQ聊天机器人&#xff08;transformer&#xff09;&#xff08;三&#xff09;功能实现的方法——NoneBot2插件结构与编写 在前两节中&#xff0c;我们详细讲解了QQ聊天的原理、QQ机器人的框架与环境配置的流程。本节将重点介绍NoneBot2的插件构成&#x…

完美解决node-sass@4.14.1 postinstall: `node scripts/build.js` 问题

node v14.16.0 安装node-sass4.14.1会出现报错 看日志排查发现设置的源国内的都有问题 直接梯子下载&#xff1a; https://github.com/sass/node-sass/releases/download/v4.14.1/win32-x64-83_binding.node 本地启动phpstudy&#xff0c;当然你也可以放在你服务器上&#xff0…

【C++11及其特性】C++类型转换

C类型转换目录 一.C语言的强制类型转换二.static_cast1.父类子类之间指针或引用的转换2.基本数据类型的转换3.空指针转换其他类型指针4.其他类型指针转换为空指针5.static_cast特点6.完整代码 三.reinterpret_cast1.数值与指针之间的转换2.不同类型指针和引用之间的转换3.reint…