【简易版tinySTL】 deque容器

2024-06-21 03:44
文章标签 容器 简易版 deque tinystl

本文主要是介绍【简易版tinySTL】 deque容器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 基本概念
    • 功能
    • 思路
      • 数据结构
      • 循环数组实现
    • 代码实现
      • 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只需要再串联一个一维数组就可以了, 效率更高

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

这篇关于【简易版tinySTL】 deque容器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1080111

相关文章

Java JUC并发集合详解之线程安全容器完全攻略

《JavaJUC并发集合详解之线程安全容器完全攻略》Java通过java.util.concurrent(JUC)包提供了一整套线程安全的并发容器,它们不仅是简单的同步包装,更是基于精妙并发算法构建... 目录一、为什么需要JUC并发集合?二、核心并发集合分类与详解三、选型指南:如何选择合适的并发容器?在多

python语言中的常用容器(集合)示例详解

《python语言中的常用容器(集合)示例详解》Python集合是一种无序且不重复的数据容器,它可以存储任意类型的对象,包括数字、字符串、元组等,下面:本文主要介绍python语言中常用容器(集合... 目录1.核心内置容器1. 列表2. 元组3. 集合4. 冻结集合5. 字典2.collections模块

Spring Boot中获取IOC容器的多种方式

《SpringBoot中获取IOC容器的多种方式》本文主要介绍了SpringBoot中获取IOC容器的多种方式,包括直接注入、实现ApplicationContextAware接口、通过Spring... 目录1. 直接注入ApplicationContext2. 实现ApplicationContextA

linux配置podman阿里云容器镜像加速器详解

《linux配置podman阿里云容器镜像加速器详解》本文指导如何配置Podman使用阿里云容器镜像加速器:登录阿里云获取专属加速地址,修改Podman配置文件并移除https://前缀,最后拉取镜像... 目录1.下载podman2.获取阿里云个人容器镜像加速器地址3.更改podman配置文件4.使用po

k8s容器放开锁内存限制问题

《k8s容器放开锁内存限制问题》nccl-test容器运行mpirun时因NCCL_BUFFSIZE过大导致OOM,需通过修改docker服务配置文件,将LimitMEMLOCK设为infinity并... 目录问题问题确认放开容器max locked memory限制总结参考:https://Access

通过Docker容器部署Python环境的全流程

《通过Docker容器部署Python环境的全流程》在现代化开发流程中,Docker因其轻量化、环境隔离和跨平台一致性的特性,已成为部署Python应用的标准工具,本文将详细演示如何通过Docker容... 目录引言一、docker与python的协同优势二、核心步骤详解三、进阶配置技巧四、生产环境最佳实践

SpringBoot结合Docker进行容器化处理指南

《SpringBoot结合Docker进行容器化处理指南》在当今快速发展的软件工程领域,SpringBoot和Docker已经成为现代Java开发者的必备工具,本文将深入讲解如何将一个SpringBo... 目录前言一、为什么选择 Spring Bootjavascript + docker1. 快速部署与

Spring IoC 容器的使用详解(最新整理)

《SpringIoC容器的使用详解(最新整理)》文章介绍了Spring框架中的应用分层思想与IoC容器原理,通过分层解耦业务逻辑、数据访问等模块,IoC容器利用@Component注解管理Bean... 目录1. 应用分层2. IoC 的介绍3. IoC 容器的使用3.1. bean 的存储3.2. 方法注

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

Linux实现简易版Shell的代码详解

《Linux实现简易版Shell的代码详解》本篇文章,我们将一起踏上一段有趣的旅程,仿照CentOS–Bash的工作流程,实现一个功能虽然简单,但足以让你深刻理解Shell工作原理的迷你Sh... 目录一、程序流程分析二、代码实现1. 打印命令行提示符2. 获取用户输入的命令行3. 命令行解析4. 执行命令