【简易版tinySTL】 deque容器

news2024/12/23 14:01:54

文章目录

    • 基本概念
    • 功能
    • 思路
      • 数据结构
      • 循环数组实现
    • 代码实现
      • deque.h
      • test.cpp
    • 代码详解
      • 变量
      • push_front
      • push_back
      • pop_front、pop_back
      • operator[]
      • clear
      • printElements
      • resize
    • 本实现版本 和 C++ STL标准库实现版本的区别:

基本概念

功能: 双端数组,可以对头端进行插入删除操作

deque与vector区别:

  • vector对于头部的插入删除效率低,数据量越大,效率越低
  • deque相对而言,对头部的插入删除速度回比vector快
  • vector访问元素时的速度会比deque快,这和两者内部实现有关

image-20240520214345386

功能

设计一个名为 Deque 的 Deque 类,该类具有以下功能和特性:

1、基础成员函数

  • 构造函数:初始化 Deque 实例
  • 析构函数:清理资源,确保无内存泄露

2、核心功能

  • 在 Deque 末尾添加元素
  • 在 Deque 开头添加元素
  • 删除 Deque 末尾的元素
  • 删除 Deque 开头的元素
  • 获取 Deque 中节点的数量
  • 删除 Deque 中所有的元素

3、迭代与遍历

  • 打印 Deque 中的元素

4、辅助功能

  • 重载[]运算符以对 Deque 进行索引访问

思路

下面代码将会使用循环数组的方式来模拟双端队列,实现了一个模板类 deque

数据结构

  • elements:指向队列元素的指针
  • frontIndex:队列第一个元素的索引
  • backIndex:指向队列最后一个元素,下一位地址的索引。如果空间满了,则指向最后一个元素
  • size:当前队列元素个数
  • capacity:队列的最大容量

循环数组实现

如下图所示:

  1. push_front(10):首先要开辟一块连续的地址空间 unuseded(与vector一样,当存储容量capacity满了之后,我们要扩展空间,扩展后的容量将会翻倍),此时frontIndexbackIndex都指向NULL。随后,该块内存会分配数据(10),然后frontIndexbackIndex都会指向这个地址。
  2. push_back(20):由于存储空间不够用了,系统会再开辟一块内存,其大小为2,分配数据(20),然后backIndex移动到该地址,frontIndex不动
  3. push_front(30):同上,系统会再开辟一段存储空间,其大小为4。由于是在队头插入,且地址是连续的,为了模拟出循环队列的效果,我们会让frontIndex指针移到队尾,并将数据(30)存在队尾。当我们想遍历元素的时候,frontIndex会沿着 ♻ 绿色箭头的方向遍历(不用担心,这其实很好实现)
  4. push_back(0):存储空间够用,则backIndex指向的地址赋值(0),然后backIndex后移一位

image-20240520193850545

代码实现

deque.h

#pragma once

#include <iostream>
#include <stdexcept>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <string>

namespace mystl{
template <class T>
class deque{
public:
    T* elements;
    size_t frontIndex;
    size_t backIndex;
    size_t size;
    size_t capacity;
    
    deque():elements(nullptr), size(0), capacity(0),frontIndex(0),backIndex(0){};
    ~deque()
    {
        size = 0;
        elements = nullptr;
        frontIndex = 0;
        backIndex = 0;
    }
    
    void push_front(const T& value)
    {
        if(size == capacity)
        {
            resize();
        }
        frontIndex = (frontIndex - 1 + capacity)%capacity;
        elements[frontIndex] = value;
        size++;
    }
    
    void push_back(const T& value)
    {
        if(size == capacity)
        {
            resize();
            
        }
        elements[backIndex] = value; // ?
        backIndex = (backIndex + 1)%capacity;
        size++;
    }
    
    void pop_front()
    {
        if(size == 0)
        {
            throw std::out_of_range("deque is empty");
        }
        frontIndex = (frontIndex+1)%capacity;
        size--;
    }
    
    void pop_back()
    {
        if(size == 0)
        {
            throw std::out_of_range("deque is empty");
        }
        backIndex = (backIndex - 1 + capacity)%capacity;
        size--;
    }
    
    T& operator[](int index)
    {
        if(index >= size)
        {
            throw std::out_of_range("deque is empty");
        }
        return elements[(frontIndex+index)%capacity];
    }
    
    size_t getSize() const
    {
        return size;
    }
    
    void clear()
    {
        while(size > 0)
        {
            pop_front();
        }
    }
    
    void printElements() const
    {
        size_t index = frontIndex;
        for(size_t i = 0; i<size;++i)
        {
            std::cout <<  elements[index] << " ";
            index = (index+1)%capacity;
        }
        std::cout << std::endl;
    }
    
private:
    void resize()
    {
        size_t newCapacity = (capacity == 0)? 1:2*capacity;
        T* newElements = new T[newCapacity];
        size_t index = frontIndex;
        for(size_t i =0; i<size; ++i)
        {
            newElements[i] = elements[index];
            index = (index+1)%capacity;
        }
        delete[] elements;
        
        elements = newElements;
        capacity = newCapacity;
        frontIndex = 0;
        backIndex = size;
    }
};
}

test.cpp

#include "deque.h"
void dequeTest()
{
    mystl::deque<int> d1;
    d1.push_front(10);
    d1.push_back(20);
    d1.push_front(30);
    d1.push_back(0);
    d1.printElements();
    std::cout << d1[2] << std::endl;
    d1.pop_back();
    d1.printElements();
    d1.pop_front();
    d1.printElements();
    d1.clear();
    d1.printElements();
}

int main()
{
    dequeTest();
    system("pause");
    return 0;
}

代码详解

变量

T* elements;
size_t frontIndex;
size_t backIndex;
size_t size;
size_t capacity;
  • elements:指向队列元素的指针
  • frontIndex:队列第一个元素的索引
  • backIndex:指向队列最后一个元素,下一位地址的索引。如果空间满了,则指向最后一个元素
  • size:当前队列元素个数
  • capacity:队列的最大容量

除了elements是T指针外,其它都是整型

push_front

记住我们赋值的1、2、3、4……都是常量,所以输入形参是 const T& value

void push_front(const T& value)
{
    if(size == capacity)
    {
    resize();
    }
    frontIndex = (frontIndex - 1 + capacity)%capacity;
    elements[frontIndex] = value;
    size++;
}
  • 在队列前端插入元素。首先检查容量并调整数组大小(如果需要)

  • 更新 frontIndexfrontIndex = (frontIndex - 1 + capacity) % capacity;

    如果frontIndex不在队头,那frontIndex往前移一位

    如果frontIndex在队头,即 frontIndex = 0,那么frontIndex-1= -1(-1 + capacity)% capcitys = capcitys - 1,也就是当前存储空间的最后一个地址

  • 在新位置插入元素,最后增加 size

push_back

void push_back(const T& value)
{
    if(size == capacity)
    {
    resize();

    }
    elements[backIndex] = value; // ?
    backIndex = (backIndex + 1)%capacity;
    size++;
}
  • 在队列后端插入元素。首先检查容量并调整数组大小(如果需要),在 backIndex位置插入元素

  • 更新 backIndexbackIndex = (backIndex + 1)%capacity

    backIndex一般都是小于capacity,因此实际情况下backIndex = backIndex + 1

  • 最后增加 size

pop_front、pop_back

void pop_front()
{
    if(size == 0)
    {
        throw std::out_of_range("deque is empty");
    }
    frontIndex = (frontIndex+1)%capacity;
    size--;
}
    
void pop_back()
{
    if(size == 0)
    {
        throw std::out_of_range("deque is empty");
    }
    backIndex = (backIndex - 1 + capacity)%capacity;
    size--;
}

从队列前(后)端移除元素。首先检查队列是否为空,然后更新 frontIndexbackIndex),最后减少 size

operator[]

T& operator[](int index)
{
    if(index >= size)
    {
    throw std::out_of_range("deque is empty");
    }
    return elements[(frontIndex+index)%capacity];
}

(frontIndex+index)%capacity

把取值范围限定在[0,capacity-1]这个区间,如下图:当frontIndex = 3,在后面时,如果index = 2,capacity = 4,则(3+2)% 4 = 1;则是 20 这个元素

image-20240520211909053

如下图:当frontIndex = 0,在后面时,如果index = 2,capacity = 4,则(0+2)% 4 = 2;则是 0 这个元素

image-20240520212242898

clear

void clear()
{
    while(size > 0)
    {
    	pop_front();
    }
}

清空队列中的所有元素,通过不断调用 pop_front 方法实现。

printElements

void printElements() const
{
    size_t index = frontIndex;
    for(size_t i = 0; i<size;++i)
    {
        std::cout <<  elements[index] << " ";
        index = (index+1)%capacity;
    }
    std::cout << std::endl;
}

打印队列中的所有元素,从 frontIndex 开始遍历,直到打印完所有元素。这里使用的index是是从frontIndex开始计算的索引, 而不是实际上的数组索引

frontIndex会沿着 ♻ 绿色箭头的方向遍历

image-20240520213130054

resize

void resize()
{
    size_t newCapacity = (capacity == 0)? 1:2*capacity;
    T* newElements = new T[newCapacity];
    size_t index = frontIndex;
    for(size_t i =0; i<size; ++i)
    {
        newElements[i] = elements[index];
        index = (index+1)%capacity;
    }
    delete[] elements;

    elements = newElements;
    capacity = newCapacity;
    frontIndex = 0;
    backIndex = size;
}

当数组容量不足以容纳更多元素时,创建一个新的数组,将现有元素复制到新数组中,释放旧数组,并更新相关成员变量。

需要注意的是, 原来的数组中, 逻辑上索引为0的位置(也就是frontIndex)并不一定存储在数组实际上的0索引处, 但resize后将逻辑索引和实际索引都统一起来。如图所示:

image-20240520214054837

本实现版本 和 C++ STL标准库实现版本的区别:

标准库中的 std::deque(双端队列)通常是通过一个或者多个连续存储区域(即一维数组)来实现的,而不是单一的连续数组, 这多个一维数组连接起来形成了deque数组, 目前的实现采用的是循环数组, 缺点就是resize时要复制旧数组, 而官方的std::deque只需要再串联一个一维数组就可以了, 效率更高

内存管理、异常安全性、迭代器、性能优化均未实现

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

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

相关文章

300PLC连接Modbus转Profibus网关与阀岛modbusRTU通讯

一、概况 300PLC作为常见的控制器设备&#xff0c;在与阀岛Modbus RTU通讯时&#xff0c;通常需要借助Modbus转Profibus网关&#xff08;XD-MDPB100&#xff09;来实现连接和数据交换。PLC通过Modbus转Profibus网关&#xff08;XD-MDPB100&#xff09;与阀岛Modbus RTU通讯是比…

无约束动态矩阵控制(DMC)

0、前言 动态矩阵控制&#xff08;Dynamic Matrix Control&#xff0c;DMC&#xff09;是一种典型的模型预测控制方法&#xff0c;其不需要被控对象的数学模型&#xff0c;只需要获取被控对象的阶跃响应序列即可实现控制效果&#xff0c;但其需要被控对象是渐近稳定的。 1、稳…

PTA - 编写函数计算薪资

某公司销售员底薪为5000&#xff0c;销售业绩与利润提成的关系如下表所示&#xff08;计量单位&#xff1a;元&#xff09; 编写函数&#xff0c;计算员工月薪。 函数接口定义&#xff1a; salary(sales) 其中 参数 sales表示员工的月销售业绩。 裁判测试程序样例&#xff…

Ranger配置图片及json文件预览

文章目录 前言下载apt下载pip下载 配置使用json文件预览方法一 修改scope用cat预览方法二 安装jq预览配置ranger 图片文件预览方法一 使用img2txt预览方法二 使用fim预览配置ranger 总结 前言 本文主要讲解Ranger12如何配置json及图片的预览设置&#xff0c;如下是ranger的介绍…

RX数据集成:信创生态下的平滑过渡方案

过去&#xff0c;众多中国企业倾向于采用国际供应商的数据集成产品与方案。其中Informatica作为行业翘楚&#xff0c;以其卓越性能和技术领先地位赢得了全球500强中95%企业的青睐。在中国市场上&#xff0c;众多企业同样信赖并采纳其解决方案。然而&#xff0c;随着国际环境的演…

数学建模基础:线性模型

目录 前言 一、线性方程组 二、线性规划 三、线性回归 四、线性模型的应用 五、实例示范&#xff1a;医疗成本预测 步骤 1&#xff1a;导入数据 步骤 2&#xff1a;数据预处理 步骤 3&#xff1a;建立多元线性回归模型 步骤 4&#xff1a;模型验证 步骤 5&#xff1…

C++多重继承,虚基类与友元

一.多重继承 就是一个类继承多个基类&#xff1b; class <派生类名>&#xff1a;<派生方式1><基类名1>,<派生方式n><基类名n> class Derived:public:Base1,public:Base2 上述形式&#xff1a;基类之间由逗号隔开&#xff0c;且必须指明继承方式…

Redis-数据类型-String

文章目录 1、通过客户端连接redis2、查看当前数据库的key的数量3、切换数据库3.1、切换到第一个数据库3.2、切换到第二个数据库3.3、切换到默认的数据库&#xff0c;第0个数据库 4、当前数据库没有数据5、添加键值对6、查看当前库所有key7、清空当前库8、设置存活的秒数&#x…

定个小目标之刷LeetCode热题(25)

这道题采用的解法是桶排序&#xff0c;画草图如下 代码如下 //基于桶排序求解「前 K 个高频元素」 class Solution {public int[] topKFrequent(int[] nums, int k) {HashMap<Integer, Integer> map new HashMap();for (int num : nums) {if (map.containsKey(num)) {m…

python自动化系列:自动复制一个工作簿的所有工作表到其他工作簿

作品介绍 作品名称&#xff1a;自动复制一个工作簿的所有工作表到其他工作簿 开发环境&#xff1a;PyCharm 2023.3.4 python3.7 用到的库&#xff1a;os、xlwings 作品效果&#xff1a; 实现过程 一、代码设计 以下是代码的详细说明&#xff1a; 导入模块&#xff1a; …

物联网主机E6000:动环监控的全新解决方案!

物联网主机E6000在动环监控中的应用&#xff0c;标志着一场新的技术革命。随着科技的进步&#xff0c;特别是在物联网领域&#xff0c;数据采集和处理已经成为企业运营不可或缺的一环。 E6000作为一款支持多协议、多接口的全能型物联网主机&#xff0c;其在动环监控领域的应用…

详解Spring AOP(二)

目录 1.切点表达式 1.1execution表达式 1.2 annotation 1.2.1自定义注解MyAspect 1.2.3添加自定义注解 2.Sping AOP原理 2.1代理模式 2.1.1静态代理 2.1.2动态代理 2.1.3JDK动态代理 2.1.4CGLIB动态代理 3.总结 承接上文&#xff1a;详解Spring AOP&#xff08;一&…

【GO】rotatelogs库和sirupsen/logrus库实现日志功能的实践用例

“github.com/sirupsen/logrus” 是一个 Go 语言的日志库&#xff0c;它提供了一种简单、灵活的方式来记录日志。该库的主要特点包括&#xff1a; 支持多种日志输出目标&#xff0c;如控制台、文件等。 支持日志轮转&#xff0c;可以按照时间或文件大小进行轮转。 支持日志格式…

【投稿优惠|稳定出版】2024年体育、健康与食品安全国际学术会议(ICSHFS 2024)

【投稿优惠|稳定出版】2024年体育、健康与食品安全国际学术会议&#xff08;ICSHFS 2024&#xff09; 2024 International Conference on Sports, Health, and Food Safety(ICSHFS 2024) 会议简介&#xff1a; 2024年体育、健康与食品安全国际学术会议&#xff08;ICSHFS 2024…

Navicat和SQLynx功能比较三(数据导出:使用MySQL近千万数据测试)

数据导出的功能在数据库管理工具中是最普遍的功能之一。所以数据导出的功能稳定性和性能也是数据库管理工具是否能很好地满足应用需求的一个考虑因素。 目录 1. 整体比较 2. 示例 2.1 前置环境 2.2 Navicat导出 2.3 SQLynx导出 2.4 性能对比结果&#xff08;690万行数据&…

用友 打印模版增加打印次数,以付款申请单为例

一些公司需要在纸质单据上加上和电子发票一样的打印次数&#xff0c;具体做法如下&#xff1a; 找到要增加的单据&#xff0c;点击【格式设置】 找到打印&#xff0c;活动文本 设置活动文本&#xff0c;高级属性&#xff0c;在下拉框里找到【打印或显示操作员/次数/时间】或【…

NetSuite 审批工作流与事务处理类型的限制关系

在最近的实践中&#xff0c;用户提出可否对Credit Memo与Vendor Prepayment Application两种事务处理类型进行审批参与&#xff0c;当提出来的时候我们并没有直接在系统中进行测试&#xff0c;而是以常规事务处理的角度认为可以满足客户的需求&#xff1b; 但在沙盒环境中讨论…

【ARMv8/v9 GIC 系列 2.1 -- GIC SPI 中断的 pending 和 clear pending 配置】

文章目录 GIC Pending 和 Clear PendingGICD_ISPENDR<n>GICD_ICPENDR<n>参数<n>编号解释使用举例设置中断ID 100为挂起状态清除中断ID 100的挂起状态 代码实现小结 GIC Pending 和 Clear Pending 在ARMv8体系结构中&#xff0c;GICD_ISPENDR<n> 和 GI…

网页抓取和网页爬取之间有何区别?

随着互联网的发展和信息的爆炸式增长&#xff0c;数据收集和处理已成为企业和个人不可或缺的需求。在此背景下&#xff0c;网页抓取和网络爬虫已成为两种常见的数据收集方法。虽然这两种方法看似相似&#xff0c;但它们的方法和目标存在显著差异。本文将为您详细介绍网页抓取和…

看见未来社区:视频孪生技术打造智慧社区

智慧社区的建设需要创新的技术支撑。智汇云舟创新升级数字孪生为视频孪生技术&#xff0c;通过将真实世界的视频监控与数字模型实时融合&#xff0c;实现了对物理空间的实时实景动态模拟。 针对智慧社区管理业务&#xff0c;以智汇云舟视频孪生平台为支撑&#xff0c;综合承载…