双端队列和C++ std::deque详解

news2024/9/26 3:23:48

文章目录

  • 1. 双端队列和std::duque
  • 2. deque的用法
    • 2.1 deque的定义和声明
    • 2.2 成员函数
      • 2.2.1 元素访问
        • assign
        • at
        • operator[]
        • front
        • back
      • 2.2.2 迭代器
        • begin、end和cbegin、cend
        • rbegin、rend和crbegin、crend
      • 2.2.3 容量
        • empty
        • size
        • max_size
        • shrink_to_fit
      • 2.2.4 修改器
        • clear
        • insert
        • emplace
        • earse
        • push_back
        • emplace_back
        • pop_back
        • push_front
        • emplace_front
        • pop_front
        • resize
        • swap
  • 3. 总结

1. 双端队列和std::duque

双端队列实际上是队列的一种变形,队列要求只能在队尾添加元素,在队头删除元素,而双端队列在队头和队尾都可以进行添加和删除元素的操作。双端队列是限定插入和删除操作在表的两端进行的线性表。C++中提供deque容器来实现双端队列的功能。

std::duque(double-venden queue, 双端队列)是C++容器库里中有下标顺序容器,它允许在首尾部两端快速的插入和删除元素。其与std::vector的存储方式不同,deque的元素不是连续存储的。

2. deque的用法

2.1 deque的定义和声明

std::deque在头文件<deque>中定义,其声明如下:

template<
    class T,
    class Allocator = std::allocator<T>
> class deque;

namespace pmr {
    template< class T >
    using deque = std::deque<T, std::pmr::polymorphic_allocator<T>>;     //C++17 起
}

其中,参数T为容器要存储的元素类型,对于T需要满足:

  • 可复制赋值和可复制构造(C++11前)。
  • 可擦除,即元素类型的对象能以给定的分配器(Allocator)销毁(C++11 起)。

Allocator为用于获取/释放内存及构造/析构内存中元素的分配器。

2.2 成员函数

2.2.1 元素访问

assign

assign函数的主要作用是将元素从 deque 中清除并将新的元素序列复制到目标deque。其函数声明如下:

//以count份value的副本替换内容。
void assign( size_type count, const T& value );

//以范围[first, last)中元素的副本替换内容。
template< class InputIt >
void assign( InputIt first, InputIt last );

//以来自initializer_list ilist的元素替换内容。
void assign( std::initializer_list<T> ilist ); //C++11 起

其具体用法如下:

std::deque<char> char_deque;

char_deque.assign(5, 'a');//此时char_deque = {'a', 'a', 'a', 'a', 'a'}

const std::string str(6, 'b');
char_deque.assign(str.begin(), str.end());//此时char_deque存储的元素分别为{'b', 'b', 'b', 'b', 'b', 'b'}

char_deque.assign({'C', '+', '+', '1', '1'});//此时char_deque存储的元素分别为{'C', '+', '+', '1', '1'}

at

at用于访问指定的元素,同时进行越界检查,该函数返回位于指定位置pos的元素的引用,如果pos不在容器的范围内,则抛出std::out_of_range异常。其函数声明如下:

reference       at( size_type pos );
const_reference at( size_type pos ) const;

其具体用法如下:

std::deque<int> data = {1, 2, 3};

std::cout<<data.at(1)<<std::endl; //2
data.at(1)=8; //此时data={1, 8, 3}

try{
  data.at(6) = 6;
}catch(std::cout_of_range const& exc){
  std::cout<<exc.what()<<std::endl; 
  //打印输出:deque::_M_range_check: __n (which is 6)>= this->size() (which is 6)
}

operator[]

operator[]与at功能相同,即用来访问指定的元素,但其与at不同的是:operator[]不进行边界的检查。其函数声明如下所示:

reference       operator[]( size_type pos );
const_reference operator[]( size_type pos ) const;

front

front用于访问容器的第一个元素,其返回值为容器首元素的引用,其函数原型如下:

reference front();
const_reference front() const;

back

back主要功能是用来访问容器最后一个元素,其返回值为容器最后一个元素的引用,其函数原型如下所示:

reference back();
const_reference back() const;

2.2.2 迭代器

begin、end和cbegin、cend

begin和cbegin返回指向deque首元素的迭代器,end和cend返回指向deque末元素后一元素的迭代器。其函数声明如下:

iterator begin(); //C++11 前
iterator begin() noexcept; //C++11 起
const_iterator begin() const; //C++11 前
const_iterator begin() const noexcept; //C++11 起
const_iterator cbegin() const noexcept; //C++11 起


iterator end(); //C++11 前
iterator end() noexcept; //C++11 起
const_iterator end() const; //C++11 前
const_iterator end() const noexcept; //C++11 起
const_iterator cend() const noexcept; //C++11 起

如果deque为空,则返回的迭代器将等于end或cend。end和cend指向deque末元素后一元素的迭代器,该元素的表现为占位符,试图访问它将导致未定义行为。

在这里插入图片描述

rbegin、rend和crbegin、crend

rbegin和crbegin返回指向deque首元素的逆向迭代器。它对应非逆向deque的末元素,若deque为空,则返回的迭代器等于rend或crend。crbegin和crend返回指向逆向deque末元素后一元素的逆向迭代器,它对应非逆向deque首元素的前一元素,此元素表现为占位符,试图访问它导致未定义行为。它们的声明如下:

reverse_iterator rbegin(); //C++11 前
reverse_iterator rbegin() noexcept; //C++11 起
const_reverse_iterator rbegin() const; //C++11 前
const_reverse_iterator rbegin() const noexcept; //C++11 起
const_reverse_iterator crbegin() const noexcept; //C++11 起

reverse_iterator rend(); //C++11 前
reverse_iterator rend() noexcept; //C++11 起
const_reverse_iterator rend() const; //C++11 前
const_reverse_iterator rend() const noexcept; //C++11 起
const_reverse_iterator crend() const noexcept; //C++11 起

在这里插入图片描述

2.2.3 容量

empty

empty用来检查容器是否为空,若为空则返回true,否则为false。其函数声明如下:

bool empty() const; //C++11 前
bool empty() const noexcept; //C++11 起, C++20 前
[[nodiscard]] bool empty() const noexcept; //C++20 起

其底层实现就是检查容器是否无元素,即判断是否begin() == end()

size

size函数返回容器中元素数量,即std::distance(begin(), end()) 。其函数声明如下:

size_type size() const; //C++11 前
size_type size() const noexcept; //C++11 起

max_size

max_size函数返回根据系统或库实现限制的容器可保有的元素最大数量,此值通常反映容器大小上的理论极限,运行时,可用 RAM 总量可能会限制容器大小到小于 max_size() 的值。其函数声明为:

size_type max_size() const; //C++11 前
size_type max_size() const noexcept; //C++11 起

shrink_to_fit

shrink_to_fit函数主要是用来请求移除未使用的容量,通过释放未使用的内存来减少对内存的使用,但其是减少使用内存而不更改序列大小的非强制请求,其请求是否达成依赖于具体实现。其函数原型如下:

void shrink_to_fit();

2.2.4 修改器

clear

clear函数主要用来擦除所有元素,使用clear()后,再次调用size(),size函数返回0。clear函数的声明如下:

void clear(); //C++11 前
void clear() noexcept; //C++11 起

insert

insert函数主要用于插入元素到容器的指定位置,其函数原型如下所示:

//在pos前插入value,其返回值为指向被插入value的迭代器
iterator insert( const_iterator pos, const T& value );

//在pos前插入value,其返回值为指向被插入value的迭代器
iterator insert( const_iterator pos, T&& value ); //C++11 起

//在pos前插入count个value,其返回值为指向首个被插入元素的迭代器,或者在 count == 0 时返回 pos。
iterator insert( const_iterator pos, size_type count, const T& value );

//在pos前插入[first, kast)的元素,如果 first 和 last 是指向 *this 中的迭代器,那么该行为未定义。
//其返回值为指向首个被插入元素的迭代器,或者在 first == last 时返回 pos
template< class InputIt >
iterator insert( const_iterator pos, InputIt first, InputIt last );

//在pos前插入来自initializer_list ilist 的元素,其返回值为指向首个被插入元素的迭代器,或者在 ilist 为空时返回 pos。
iterator insert( const_iterator pos, std::initializer_list<T> ilist ); //C++11 起

具体用法示例如下:

std::deque<int> c1(3, 100); //初始化一个int行的双端队列c1,此时c1 = {100, 100, 100}

auto it = c1.begin();
it = c1.insert(it, 200); //在it前插入元素200
//c1 = {200,100, 100, 100}

c1.insert(it, 2, 300); //在it前插入两个元素值都为300
//c1 = {300,300,200,100, 100, 100}

// 将 it 重新指向开头
it = c1.begin();

std::deque<int> c2(2, 400); //c2 = {400, 400}
c1.insert(std::next(it, 2), c2.begin(), c2.end()); //在it后两个元素(即200)的前面插入c2
//c1 = {300,300,400,400,200,100, 100, 100}

int arr[] = {501, 502, 503};
c1.insert(c1.begin(), arr, arr + std::size(arr));
//c1 = {501,502,503,300,300,400,400,200,100, 100, 100}

c1.insert(c1.end(), {601, 602, 603});
//c1 = {501,502,503,300,300,400,400,200,100, 100, 100,601,602,603}

emplace

emplace函数的声明如下:

/*----------------------------------
  pos:将构造新元素到其前的迭代器
  args:转发给元素构造函数的参数
  返回值iterator:指向被安置的元素的迭代器
------------------------------------*/
template< class... Args >
iterator emplace( const_iterator pos, Args&&... args ); //C++11 起

其主要作用就是原位构造元素并将其在pos前插入到容器中。

earse

earse的函数主要功能是擦除元素,其声明如下:

//移除位于pos的元素
//返回值:最后移除元素之后的迭代器。如果pos指代末元素,则返回end()迭代器
iterator erase( iterator pos ); //C++11 前
iterator erase( const_iterator pos ); //C++11 起

//移除范围[first, last)中的元素。
/*返回值:最后移除元素之后的迭代器。
         如果在移除前last == end(),那么最终返回end()迭代器
         如果范围[first, last) 为空,那么返回 last。*/
iterator erase( iterator first, iterator last ); //C++11 前
iterator erase( const_iterator first, const_iterator last ); //C++11 起

具体的用法如下所示:

std::deque<int> c{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

c.erase(c.begin());
//c = {1, 2, 3, 4, 5, 6, 7, 8, 9}

c.erase(c.begin() + 2, c.begin() + 5);
//c = {1, 2, 6, 7, 8, 9}

// 移除所有偶数
for (std::deque<int>::iterator it = c.begin(); it != c.end();)
{
  if (*it % 2 == 0)
    it = c.erase(it);
  else
    ++it;
}
//c = {1, 7, 9}

push_back

push_back函数的主要作用是将元素添加到容器末尾,其声明如下:

void push_back( const T& value );
void push_back( T&& value ); //C++11 起

emplace_back

emplace_back函数与emplace类似,只不过是在容器末尾就地构造元素,其函数声明如下:

template< class... Args >
void emplace_back( Args&&... args ); //C++11 起, C++17 前
template< class... Args >
reference emplace_back( Args&&... args ); //C++17 起

由于emplace_back是原地构造元素,因此其插入效率要高于push_back。

pop_back

pop_back函数的主要作用就是移除末元素,其函数声明如下:

void pop_back();

如果在空容器上调用pop_back会导致未定义行为。

push_front

push_front函数的主要作用就是插入元素到容器起始位置,其函数原型如下:

void push_front( const T& value );
void push_front( T&& value ); //C++11 起

emplace_front

emplace_front函数的作用是在容器头部原位构造元素,即插入新元素到容器起始,由于其也是在容器所提供的位置原位构造函数,因此其效率也高于push_front。其函数声明为:

template< class... Args >
void emplace_front( Args&&... args ); //C++11 起, C++17 前
template< class... Args >
reference emplace_front( Args&&... args ); //C++17 起

pop_front

pop_front函数主要作用是移除容器首元素。若容器中无元素,则行为未定义。其函数声明为:

void pop_front();

resize

resize函数的主要作用是改变容器中可存储元素的个数,通过该函数可以重新设置容器大小,其函数声明如下:

/*
该函数重设容器的大小为count,在count==size()时不做任何操作。
如果当前大小大于 count,那么减小容器到它的开头 count 个元素。
如果当前大小小于 count,那么后附额外的默认插入的元素。
*/
void resize( size_type count );

/*
该函数重设容器的大小为count,在count==size()时不做任何操作。
如果当前大小大于 count,那么减小容器到它的开头 count 个元素。
如果当前大小小于 count,那么后附额外的 value 的副本
*/
void resize( size_type count, const value_type& value );

其具体用法示例如下:

std::deque<int> c = {1, 2, 3};

c.resize(5); //将其size增加大小到5
//c = {1, 2, 3, 0, 0}

c.resize(2); //将其size减少大小到2
//c = {1, 2}

c.resize(6, 4); //将其size增加大小到6,填充值为4";
//c = {1, 2, 4, 4, 4,4}

swap

swap函数的主要作用是交换两个deque容器的内容,不在单独的元素上调用任何移动、复制或交换操作。所有迭代器和引用保持有效。end()迭代器会失效。其函数声明如下:

void swap( deque& other ); //C++17 前
void swap( deque& other ) noexcept(); //C++17 起

其用法示例如下图所示:

std::deque<int> a1{1, 2, 3}, a2{4, 5};

auto it1 = std::next(a1.begin()); //*it1 = 2 
auto it2 = std::next(a2.begin()); //*it2 = 5 

int& ref1 = a1.front(); //ref1 = 1
int& ref2 = a2.front(); //ref1 = 4

std::cout <<*it1 << ' ' << *it2 << ' ' << ref1 << ' ' << ref2 << '\n';
//打印结果为2 5 1 4

a1.swap(a2);

//此时a1 = {4, 5},a2 = {1, 2, 3}
std::cout <<*it1 << ' ' << *it2 << ' ' << ref1 << ' ' << ref2 << '\n';
//打印结果仍为2 5 1 4

/*注:
    交换后迭代器与引用保持与原来的元素关联,
    例如尽管 'a1' 中值为 2 的元素被移动到 'a2' 中,
    原来指向它的 it1 仍指向同一元素。*/

3. 总结

双端队列的的优劣:

优点

  • 支持恒定时间内随机访问,且开销小。
  • 支持快速遍历,适合线性搜索。
  • 两端插入和删除性能好。
  • 插入不会使指向元素的引用/指针无效。

劣势

  • 如果在随机位置的插入/擦除操作占主导地位,则可能会变慢。
  • 如果元素类型具有较高的复制/分配成本,则可能会变慢(重新排序元素需要复制/移动它们)
  • 对于非常大量的值,分配时间可能很长。

文章首发公众号:iDoitnow如果喜欢话,可以关注一下

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

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

相关文章

VLAN原理配置

VLAN原理&配置 一、VLAN的工作原理1.1、传统以太网1.2、VLAN技术1.3、VLAN帧格式1.4、VLAN接口类型1.4.1、端口类型-Access1.4.2、端口类型-Trunk1.4.3、端口类型-Hybrid 1.5、PVID&#xff08;缺省VLAN&#xff09;1.6、VLAN划分方法 二、VLAN的基本配置2.1、VLAN的配置2.…

这些代码,差点把我气出内伤

大家好&#xff0c;我是鱼皮&#xff0c;一个正在烂代码的泥潭里面摸爬滚打的程序员。 先问大家一个小问题&#xff1a;你觉得看别人代码累&#xff0c;还是自己写代码累&#xff1f; 我相信有很多朋友会说&#xff0c;当然是自己写代码累了&#xff0c;要思考逻辑、要动手敲…

(13)【MyBatis的启动流程与Spring boot配置】

一、Mybatis的启动流程 加载Mapper配置的映射文件或者注解相关sql内容创建会话工厂&#xff0c;MyBatis通过读写配置文件中的数据源信息来构造会话工厂创建会话&#xff0c;Mybatis通过会话工厂创建会话对象&#xff0c;会话对象是个接口&#xff0c;包含对数据库的增删改查方法…

5.8.4 TCP连接管理(二)TCP链接释放

5.8.4 TCP连接管理&#xff08;二&#xff09;TCP链接释放 在数据传输结束之后&#xff0c;通信双方都可以发出释放连接的请求&#xff0c;如图 图中所示的主机A192.168.1.100与百度服务器B&#xff1a;119.75.218.70之间TCP连接释放的过程。 主机B的TCP通知对方要释放从B到…

go开源项目slgserver源码分析

个人博客地址: https://cxx001.gitee.io 前言 项目开源地址&#xff1a;https://github.com/llr104/slgserver 比较适合作为go语言入门学习项目或轻量级游戏项目&#xff0c;整体的项目结构和编码质量还是可以的。不过距离商业项目还是差点意思&#xff0c;如服务负载、容灾这…

ajax实现跳转页面或返回上一页面并刷新

返回上一页面并刷新 window.location.href document.referrer; 跳转至另一个页面 window.location.href ‘/admin’; 图为登录页面实现时的代码

【OpenCV】获取各种分类器的haarcascades_*.XML 文件的详细介绍

文章目录 获取方式下载opencv获取直接下载 文件说明 获取方式 下载opencv获取 安装 OpenCV并获取xml文件&#xff1a; 首先&#xff0c;请参考Windows下 OpenCV 的下载安装教程&#xff08;详细&#xff09;中的步骤&#xff0c;下载 OpenCV。 一旦下载完成&#xff0c;请找到…

Mentor PADS创建BGA IC封装

创建BGA IC封装也是可以使用PCB封装向导去进行设置创建。 1、点击“绘图工具栏”图标&#xff0c;弹出对应的分列&#xff0c;点击“向导”&#xff0c;弹出“Decal Wizard”对话框&#xff0c;如图1所示。 图1“Decal Wizard”对话框 2、然后点击左上角的BGA/PGA选项&#xf…

【算法】贡献法相关题目练习

文章目录 贡献法介绍相关题目2104. 子数组范围和907. 子数组的最小值之和1856. 子数组最小乘积的最大值2681. 英雄的力量2281. 巫师的总力量和 相关链接 本文介绍一些 使用 贡献法 的算法题目。 TODO &#xff1a;先把框架写了&#xff0c;文章内容后续会补上。 贡献法介绍 相…

【李宏毅机器学习2021春】01-机器学习基本概念介绍

01 - 机器学习基本概念介绍 1. 机器学习的基本任务 1.1 Regression 回归 如&#xff0c;输入过去的数据&#xff0c;对未来的数据进行预测。对数据进行拟合的过程叫做回归。 1.2 Classifiation 分类 给出选项&#xff0c;函数输出正确的选项。 如&#xff0c;下棋&#x…

postgresql 获取建表信息

通过函数获取 创建自定义函数 CREATE OR REPLACE FUNCTION tabledef(text,text) RETURNS text LANGUAGE sql STRICT AS $$ WITH attrdef AS (SELECT n.nspname, c.relname, c.oid, pg_catalog.array_to_string(c.reloptions || array(select toast. || x from pg_catalog.un…

7.1.9 【Linux】XFS 文件系统简介

EXT当前的缺点&#xff1a;支持度最广&#xff0c;但格式化超慢 Ext 文件系统家族对于文件格式化的处理方面&#xff0c;采用的是预先规划出所有的 inode/block/metadata 等数据&#xff0c;未来系统可以直接取用&#xff0c; 不需要再进行动态配置的作法。这个作法在早期磁盘…

14 动态主题类型Dynamic Topic Types

14 动态主题类型Dynamic Topic Types eProsima Fast DDS提供了一种动态方式来定义和使用主题类型和主题数据。我们的实现遵循用于DDS接口的OMG可扩展和动态主题类型。有关更多信息,您可以阅读DDS XTypes V1.2的规范。 动态主题类型提供了在没有与IDL相关的限制的情况下通过RTP…

vscode文档搜索机制

vscode的全局搜索速度非常快&#xff0c;其中的奥妙是什么&#xff1f; 我们常常需要在一大堆文本文件里搜索一个字符串&#xff0c;在Linux下可以用自带的grep&#xff0c;不过grep的命令行还是有点难记。ripgrep是开源工具&#xff0c;使用Rust编写&#xff0c;全平台支持。看…

工业RFID在自动化控制中的解决方案

在工业自动化控制领域中&#xff0c;利用RFID技术可以对物品、设备和工具的进行追踪&#xff0c;可以有效提高生产效率和管理水平。下面我们就一起来了解一下&#xff0c;RFID在工业自动化控制中的解决方案是什么样的。 工业RFID在自动化控制中的解决方案 在工业生产过程中&a…

ai图片转绘画怎么转?分享几个ai图片生成绘画软件给你

小伙伴们&#xff0c;我今天要给你分享一些超酷的东西——ai图片生成器&#xff0c;它们就像是一个神奇的艺术宝盒&#xff0c;可以将你的文字变成令人瞠目结舌的艺术作品。你有没有想象过可以用ai技术让你的文字爆发出无限创意和惊人的艺术风格&#xff1f;没错&#xff0c;这…

共享打印机突然出现0x00000771

操作无法完成0x00000771 当多台电脑共享一台打印机时&#xff0c;突然出现了操作无法完成0x00000771。 解决方法为&#xff1a; 在直接连接打印机的电脑上进行操作&#xff1a; 选中“此电脑”&#xff0c;右键选中“管理” 打开“服务与应用程序” 选择" 服务 ",找…

与 TDengine 性能直接相关——3.0 的落盘机制优化及使用原则

许多用户会有一个疑问&#xff0c;“落盘”俩字听起来就很底层&#xff0c;似乎无法和手头的性能问题联系到一起&#xff0c;本篇文章的目的就是让大家对它们俩建立起直观的认识。 写到数据库的数据总要保存起来——所以时序数据库&#xff08;Time Series Database&#xff0…

移动隔断屏风墙,无地轨设计,空间灵活应用

移动隔断屏风墙是一种非常适合办公室的设计选择&#xff0c;它可以提供灵活的办公空间布局&#xff0c;并且无地轨设计可以避免地面安装轨道&#xff0c;给空间带来更大的自由度。以下是一些关于移动隔断屏风墙的特点和设计建议&#xff1a; 1. 灵活应用&#xff1a;移动隔断屏…

JavaScript多功能甘特图组件 - jsGantt

jsGantt 是一个可定制的、灵活的、多语言的甘特图组件&#xff0c;由原生 JavaScript 构建。它使用客户端渲染以获得快速的性能和动态的交互性。非常适用于任何需要交互式时间线或时间表显示的项目。 更多特点 多语言支持&#xff1a; 通过对多语言的支持&#xff0c;使您的甘…