C++ STL库_双端队列dqueue

一、std::deque的核心性质

deque(Double-Ended Queue)是 STL 中兼顾 “随机访问” 和 “双端高效操作” 的动态容器,核心特性如下:

  1. 双端操作高效:队首 / 队尾的插入、删除操作均为O(1)时间复杂度(平均);
  2. 随机访问支持:可以像vector一样通过下标[]at()访问元素,时间复杂度O(1);
  3. 分段连续内存:底层不是单一连续内存块,而是 “分段连续 + 中控器管理” 的结构,避免了vector整体扩容的内存拷贝开销;
  4. 动态扩容:首尾均可扩展,扩容时仅需新增缓冲区(无需移动已有元素),扩容成本远低于vector
  5. 迭代器特性:提供随机访问迭代器(和vector一致),但迭代器内部会记录缓冲区信息,比vector迭代器稍复杂;
  6. 无内存浪费:相比vector的 “预分配多余空间”,deque的缓冲区大小固定,内存利用率更高(但中控器会占用少量额外内存);
  7. 中间操作低效:中间位置的插入 / 删除仍需移动元素(或跨缓冲区调整),时间复杂度O(n)(和vector类似,比list差)。

二、deque的底层存储结构(核心难点)

deque的底层结构是vectorlist的 “折中设计”,理解它能解释其所有特性,结构分为两部分:

1. 核心结构(可视化)

2. 各部分详解
组件本质作用
中控器(Map)动态数组(存储T*类型指针)管理所有缓冲区的地址,相当于 “索引表”,中控器自身扩容时仅拷贝指针(成本极低);
缓冲区(Buffer)固定大小的连续内存块(存储实际元素)每个缓冲区存储相同数量的元素(通常默认 512 字节,可通过编译器调整),元素在缓冲区内连续存储;
3. 关键操作的底层逻辑
  • 随机访问(deque[i]

    1. 计算i属于哪个缓冲区(i / 缓冲区大小)→ 找到中控器中对应的指针;
    2. 计算i在缓冲区中的偏移(i % 缓冲区大小)→ 定位到具体元素;
    3. 全程O(1),仅多两次算术运算,和vector几乎无性能差异。
  • 队首插入(push_front

    1. 若头部缓冲区有空闲空间,直接在头部插入;
    2. 若头部无空间,新增一个缓冲区,将其指针加入中控器头部,再插入元素;
    3. 无需移动已有元素,O(1)复杂度。
  • 扩容(对比vector

    • vector:扩容时需分配新的更大连续内存,拷贝所有元素,释放旧内存(O(n)成本);
    • deque:扩容时仅需新增缓冲区,中控器若满则扩容(仅拷贝指针),已有元素无需移动(O(1)成本)。

三、std::deque的常用接口(分类详解)

使用deque需包含头文件:#include <deque>,以下是最常用的接口,按功能分类讲解。

1. 构造函数
构造方式说明
deque<T> d;默认构造,空双端队列
deque<T> d(n);构造包含n个默认构造元素的队列
deque<T> d(n, val);构造包含n个值为val的元素的队列
deque<T> d(d2);拷贝构造,复制d2的所有元素
deque<T> d(begin, end);范围构造,复制[begin, end)区间内的元素(支持数组 / 其他容器迭代器)
deque<T> d(std::move(d2));移动构造,接管d2的资源(C++11+)
2. 元素访问(支持随机访问)
接口说明
d[i]访问第i个元素(无越界检查,越界会导致未定义行为)
d.at(i)访问第i个元素(带越界检查,越界抛出out_of_range异常)
d.front()返回队首元素的引用(可修改)
d.back()返回队尾元素的引用(可修改)
d.data()返回指向第一个缓冲区的指针(C++17+,极少用)
3. 容量相关
接口说明
d.empty()判断队列是否为空,返回bool
d.size()返回元素个数,类型size_t
d.max_size()返回理论最大可存储元素数(受内存限制)
d.resize(n)调整队列大小为n:若n>size(),新增默认构造元素;若n<size(),删除尾部元素
d.resize(n, val)调整大小为n,新增元素值为val
d.shrink_to_fit()释放未使用的缓冲区内存(C++11+,仅为建议,编译器可忽略)
4. 修改操作(核心,双端特性体现)
接口说明
d.push_front(val)队首插入元素val(拷贝 / 移动构造)
d.push_back(val)队尾插入元素val(拷贝 / 移动构造)
d.emplace_front(args...)队首直接构造元素(无需先创建对象,更高效,C++11+)
d.emplace_back(args...)队尾直接构造元素(C++11+)
d.pop_front()删除队首元素(无返回值)
d.pop_back()删除队尾元素(无返回值)
d.insert(pos, val)在迭代器pos位置插入val,返回新元素的迭代器
d.insert(pos, n, val)pos位置插入nval
d.insert(pos, begin, end)pos位置插入[begin, end)区间的元素
d.erase(pos)删除pos位置的元素,返回下一个元素的迭代器
d.erase(begin, end)删除[begin, end)区间的元素,返回下一个元素的迭代器
d.swap(d2)交换两个deque的内容(中控器 + 缓冲区指针,成本极低)
d.clear()清空所有元素(释放缓冲区内存,中控器保留)
5. 迭代器(支持随机访问)
接口说明
d.begin()返回指向第一个元素的普通迭代器
d.end()返回指向最后一个元素下一位的普通迭代器
d.cbegin()返回指向第一个元素的常量迭代器(只读,C++11+)
d.cend()返回指向最后一个元素下一位的常量迭代器(只读)
d.rbegin()返回指向最后一个元素的反向迭代器
d.rend()返回指向第一个元素前一位的反向迭代器

四、std::deque完整使用示例

以下代码覆盖deque的核心接口,可直接编译运行:

#include <iostream>
#include <deque>
#include <string>
using namespace std;

// 自定义结构体,演示emplace系列接口
struct Student {
    string name;
    int score;
    // 构造函数
    Student(string n, int s) : name(n), score(s) {}
};

int main() {
    // ========== 1. 构造deque ==========
    deque<int> d1; // 默认构造
    deque<int> d2(3, 10); // 3个10:[10,10,10]
    int arr[] = {1,2,3,4};
    deque<int> d3(arr, arr+4); // 范围构造:[1,2,3,4]

    // ========== 2. 元素访问 ==========
    cout << "d3[1] = " << d3[1] << endl; // 2(无越界检查)
    cout << "d3.at(2) = " << d3.at(2) << endl; // 3(带越界检查)
    cout << "d3队首:" << d3.front() << endl; // 1
    cout << "d3队尾:" << d3.back() << endl; // 4

    // 修改元素
    d3[3] = 40;
    cout << "修改后d3队尾:" << d3.back() << endl; // 40

    // ========== 3. 双端插入/删除 ==========
    d1.push_front(5); // 队首插入5:[5]
    d1.push_back(6);  // 队尾插入6:[5,6]
    d1.emplace_front(4); // 队首直接构造4:[4,5,6]
    d1.emplace_back(7);  // 队尾直接构造7:[4,5,6,7]

    cout << "d1插入后大小:" << d1.size() << endl; // 4

    d1.pop_front(); // 删除队首:[5,6,7]
    d1.pop_back();  // 删除队尾:[5,6]
    cout << "d1删除后:" << d1[0] << "," << d1[1] << endl; // 5,6

    // ========== 4. 中间插入/删除 ==========
    // 获取指向第二个元素的迭代器
    auto it = d3.begin() + 1;
    d3.insert(it, 99); // 在位置1插入99:[1,99,2,3,40]
    cout << "插入99后d3:";
    for (int num : d3) cout << num << " "; // 1 99 2 3 40
    cout << endl;

    d3.erase(d3.begin() + 1); // 删除位置1的99:[1,2,3,40]
    cout << "删除99后d3:";
    for (int num : d3) cout << num << " "; // 1 2 3 40
    cout << endl;

    // ========== 5. 自定义类型示例 ==========
    deque<Student> d_stu;
    // emplace_back直接传构造参数,无需先创建Student对象
    d_stu.emplace_back("小明", 95);
    d_stu.emplace_front("小红", 98);
    cout << "第一个学生:" << d_stu.front().name << ",分数:" << d_stu.front().score << endl; // 小红 98

    return 0;
}

运行结果

d3[1] = 2
d3.at(2) = 3
d3队首:1
d3队尾:4
修改后d3队尾:40
d1插入后大小:4
d1删除后:5,6
插入99后d3:1 99 2 3 40 
删除99后d3:1 2 3 40 
第一个学生:小红,分数:98

五、deque vs vector vs list(核心对比)

特性dequevectorlist
随机访问支持(O(1))支持(O(1),更高效)不支持(O(n))
队首插入 / 删除O(1)(高效)O(n)(需移动元素)O(1)(高效)
队尾插入 / 删除O(1)(高效)O(1)(平均,扩容时O(n))O(1)(高效)
中间插入 / 删除O(n)(需移动元素)O(n)(需移动元素)O(1)(仅调整指针)
内存连续性分段连续完全连续不连续(节点 + 指针)
扩容成本极低(仅新增缓冲区)高(整体拷贝元素)无扩容成本(节点动态分配)
内存利用率较高(固定缓冲区)较低(预分配多余空间)低(额外指针开销)
迭代器稳定性插入 / 删除首尾时稳定,中间操作可能失效扩容时迭代器全部失效,中间操作局部失效所有操作迭代器均稳定(除被删除节点)
使用场景建议
  • deque:需要随机访问 + 双端高效操作(如实现队列 / 栈、滑动窗口);
  • vector:需要极致的随机访问性能 + 仅队尾操作(如存储大量数据、数值计算);
  • list:需要大量中间插入 / 删除 + 迭代器稳定性(如频繁修改的链表结构)。

总结

  1. std::deque是分段连续的双端队列,兼顾vector的随机访问和list的双端高效操作,扩容成本极低;
  2. 核心接口支持双端插入(push_front/push_back)、双端删除(pop_front/pop_back)和随机访问([]/at());
  3. 使用场景:优先用于需要 “首尾操作 + 随机访问” 的场景(如队列 / 栈的底层实现、滑动窗口算法),避免用于大量中间插入 / 删除的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值