C++ 双端队列 deque

双端队列简介

双端队列是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从队列的两端推入,两端弹出。通过合理使用双端队列,可以做到单独使用栈或队列的功能,也可以完成更加复杂的任务。

事实上,C++ 中的队列 queue 和 栈 stack 的底层实现就是 std::deque。

C++ deque 简介

std::deque 是有索引的序列容器,允许在它的首尾两端快速插入及删除。与 std::vector 不同,deque 的元素不是连续存储的:典型实现采用一系列单独分配的固定尺寸数组,外加额外的簿记数据。

此外,deque 的存储按需自动扩张及收缩。扩张 deque 比扩张 std::vector 更轻便,因为它不涉及将既存元素复制到新内存位置。另一方面,deque 是典型的拥有较大的最小内存开销的结构(可能是存储对象尺寸的 8 倍或 16 倍,取决于具体实现),因为就算只保存一个元素, deque 也必须分配它的整个内部数组。

deque 上常见操作的复杂度(效率)如下:

随机访问——常数 O(1)。
在结尾或起始插入或移除元素——常数 O(1)。
插入或移除元素——线性 O(n)。

使用 deque

导入 deque

deque 在 < deque > 中定义为:

// C++ 17 之前的定义
template<
    class T,
    class Allocator = std::allocator<T>
> class deque;
// C++ 17 之后的定义
namespace pmr {
    template< class T >
    using deque = std::deque<T, std::pmr::polymorphic_allocator<T>>;
}

使用时要加入头文件 < deque >,同时,作为标准库,使用时还要使用名称空间 std。

// 头文件
#include <deque>
// 使用名称空间 std
using namespace std;
// 或者
using std::deque;

创建 deque 容器

deque 容器的创建依赖其的构造函数,下面用 int 类型举例,介绍几个常用的构造函数:

  1. 默认构造函数。构造拥有默认构造的分配器的空容器。
// 函数声明原型为:deque();
deque<int> deq;
  1. 构造拥有 count 个值 value 的元素的容器。
// 函数声明原型为:deque( size_type count, const T& value = T(), const Allocator& alloc = Allocator() );
deque<int> deq(8653, 1);
// 如果不填入第二个参数,值 value 将使用默认值
  1. 复制构造函数。构造拥有 other 内容副本的容器。
// 函数声明原型为:deque( const deque& other );
deque<int> deq_1(100, 1);
deque<int> deq_2(deq_1);

元素访问

  1. 访问指定的元素(不进行边界检查)
deque<int> deq(8);
// 函数声明原型为:reference operator[]( size_type pos );
// 返回位于指定位置 pos 的元素的引用。不进行边界检查。
deq[3];
  1. 访问第一个元素
deque<int> deq(78);
// 函数声明原型为:reference front();
// 返回到容器首元素的引用。
// 注意:在空容器上调用 front 将导致出现未定义行为(未知风险)!
deq.front();
  1. 访问最后一个元素
deque<int> deq(78);
// 函数声明原型为:reference back();
// 返回到容器中最后一个元素的引用。
// 注意:在空容器上调用 back 将导致出现未定义行为(未知风险)!
deq.back();

遍历容器

遍历 deque 容器有三种方式:下标遍历,迭代器遍历,范围 for 循环遍历。

下标遍历

由于双端队列下标与双端队列中的元素一一对应,所以,通过控制下标的值,即可完成对 deque 的遍历。
下面是使用示例:

deque<int> deq(78);
// 正向遍历
for (size_t i = 0; i < deq.size(); ++i)
{
	// 内部循环体代码(省略)
}

// 反向遍历
for (long long i = deq.size() - 1; i >= 0; --i)
{
	// 内部循环体代码(省略)
}

迭代器遍历

C++ deque 类为我们提供了迭代器,我们可以借助它方便快捷的完成对deque 容器的遍历。
下面是几个常用的迭代器和使用示例:

deque<int> deq(78);
// 函数声明原型为:const_iterator begin() const;
// 返回指向 deque 首元素的迭代器。
// 如果 deque 为空,那么返回的迭代器等于 end()。
deq.begin();

// 函数声明原型为:const_iterator end() const; 
// 返回指向 deque 末元素后一元素的迭代器。
// 注意:此元素为占位符;不要试图访问它!
deq.end();

// 函数声明原型为:const_reverse_iterator rbegin() const;
// 返回指向逆向的 deque 的首元素的逆向迭代器。它对应非逆向 deque 的末元素。
// 如果 deque 为空,那么返回的迭代器等于 rend()。
deq.rbigin()

// 函数声明原型为:const_reverse_iterator rend() const;
// 返回指向逆向的 deque 末元素后一元素的逆向迭代器。它对应非逆向 deque 首元素的前一元素。
// 注意:此元素表现为占位符,不要试图访问它!
deq.rend()// 正向遍历
for (auto it = deq.begin(); it != deq.end(); ++it)
{
	// 内部循环体代码(省略)
}

// 反向遍历
for (auto it = deq.rbigin(); it != deq.rend(); ++it)
{
	// 内部循环体代码(省略)
}

范围 for 循环遍历

范围 for 循环可以方便快捷的遍历容器中的元素,这里不过多介绍,我们直接看使用示例:

deque<int> deq(78);
// 正向遍历
for (auto &it : deq)
{
	// 内部循环体代码(省略)
}
// 等价于
for (auto it = deq.begin(); it != deq.end(); ++it)
{
	// 内部循环体代码(省略)
}
// 其中,上式中的 it 等价于下式中的 *it

容器容量相关

  1. 检查容器是否为空
deque<int> deq(78);
// 函数声明原型为:bool empty() const;
// 检查容器是否无元素
deq.empty();
  1. 返回容器元素数量
deque<int> deq(78);
// 函数声明原型为:size_type size() const;
// 返回容器中的元素数
deq.size();

容器内容修改相关

插入元素

  1. 插入元素
deque<int> deq(78);
// 函数声明原型为:iterator insert( const_iterator pos, const T& value );
// 在 pos 前插入 value。(pos 是vector 的迭代器)
deq.insert(deq.begin(), 20);

// 函数声明原型为:iterator insert( const_iterator pos, size_type count, const T& value );
// 在 pos 前插入 value 的 count 个副本。(pos 是vector 的迭代器)
deq.insert(deq.begin() + 7, 21, 3);
  1. 原位构造元素
deque<int> deq(78);
// 使用方法大致与 insert 相同,函数原型略
// 效率在一般情况下优于 insert
deq.emplace(deq.begin(), 23);
  1. 将元素添加到容器末尾
deque<int> deq(78);
// 函数声明原型为:void push_back( const T& value );
// 追加给定元素 value 到容器尾。
deq.push_back(18);
  1. 在容器末尾原位构造元素
deque<int> deq(78);
// 添加新元素到容器尾。使用方法大致与 push_back 相同,函数原型略
// 效率在一般情况下优于 push_back
deq.emplace_back(18);
  1. 插入元素到容器起始
deque<int> deq(78);
// 函数声明原型为:void push_front( const T& value );
// 前附给定元素 value 到容器起始。
deq.push_front(12);
  1. 在容器头部原位构造元素
deque<int> deq(78);
// 添加新元素到容器头。使用方法大致与 push_front 相同,函数原型略
// 效率在一般情况下优于 push_front
deq.emplace_front(12);

注意:上述所有插入操作均会导致插入前的迭代器失效!但引用不会失效。

清除元素

  1. 清除内容
deque<int> deq(78);
// 函数声明原型为:void clear();
// 从容器擦除所有元素。此调用后 size() 返回零。
// 使任何指代所含元素的引用、指针和迭代器失效。
deq.clear();
  1. 擦除元素
deque<int> deq(78);
// 函数声明原型为:iterator erase( iterator pos );
// 移除位于 pos 的元素。(pos 是vector 的迭代器)
// 注意:pos 不能等于 vector::end() 所指向的迭代器
deq.erase(deq.begin() + 9);

// 函数声明原型为:iterator erase( iterator first, iterator last );
// 移除范围 [first, last) 中的元素。(first, last 是 vector 的迭代器)
deq.erase(deq.begin(), deq.begin() + 78);

// 注意:使用以上函数后,所有迭代器和引用都会失效,除非被擦除的元素在容器的开头或末尾,此时只有指向被擦除元素的迭代器或引用会失效。
  1. 移除末元素
deque<int> deq(78);
// 函数声明原型为:void pop_back();
// 移除容器的末元素。不要在空容器上调用此函数!
// 指向被擦除元素的迭代器和引用会失效。其他引用和迭代器不受影响。
deq.pop_back();
  1. 移除首元素
deque<int> deq(78);
// 函数声明原型为:void pop_front();
// 移除容器的首元素。不要在空容器上调用此函数!
// 指向被擦除元素的迭代器和引用会失效。其他迭代器和引用不受影响。

参考资料

https://zh.cppreference.com/w/cpp/container/deque

  • 32
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值