【C++】stack_queue与deque模版(模拟实现+认识+对比)

目录

[C++ STL deque 深度剖析](#C++ STL deque 深度剖析)

[一、deque 是什么](#一、deque 是什么)[二、deque 核心特性总览](#二、deque 核心特性总览)

三、底层底层原理(博客重点)[四、deque 常用接口大全](#四、deque 常用接口大全)

五、三大容器深度对比[六、迭代器失效规则(面试 + 博客重点)](#六、迭代器失效规则(面试 + 博客重点))


C++ STL deque 深度剖析

一、deque 是什么

**std::deque**全称 double-ended queue,译作双端队列,是 C++ STL 中序列式容器之一。

一句话定义:

deque 是一种支持头尾常数时间插入删除、支持随机访问、底层分段连续的序列容器,兼具 vectorlist 的优势。


二、deque 核心特性总览

  1. 头尾插入 / 删除 O (1),效率极高;
  2. 支持随机访问,可用 []at() 访问元素,像数组一样;
  3. 底层分段连续内存,不是整块连续,也不是双向链表;
  4. 中间位置插入 / 删除为 O (n),效率低;
  5. 迭代器失效规则比 vector 更温和;
  6. 自动扩容,无需手动管理内存。

三、底层底层原理

**存储模型:**中控数组 + 数据缓冲区

deque 底层采用 map 中控表 + 多个固定大小缓冲区 的结构:

  • 缓冲区(buffer):每块是固定大小的连续数组,真正存数据;
  • 中控数组(map):是一个指针数组,每个元素存放一块缓冲区的起始地址。

四、deque 常用接口大全

复制代码
deque<int> d1;                // 空双端队列
deque<int> d2(5, 10);         // 5个元素,初始值10
deque<int> d3 = {1,2,3,4};    // 初始化列表构造
deque<int> d4(d3.begin(), d3.end()); // 迭代器区间构造
dq.push_back(x);    // 尾部插入
dq.push_front(x);   // 头部插入(vector没有)
dq.pop_back();      // 尾部删除
dq.pop_front();     // 头部删除
dq[0];              // 下标随机访问,不越界检查
dq.at(1);           // 带越界检查,越界抛异常
dq.front();         // 获取队首元素
dq.back();          // 获取队尾元素
dq.size();          // 有效元素个数
dq.empty();         // 判断是否为空
dq.clear();         // 清空所有元素

// 普通遍历
for(auto it = dq.begin(); it != dq.end(); ++it)
    cout << *it << " ";

// 范围for遍历
for(auto x : dq)
    cout << x << " ";

dq.insert(dq.begin()+2, 99);  // 指定位置插入
dq.erase(dq.begin()+1);       // 指定位置删除

五、三大容器深度对比

特性 vector deque list
底层结构 整块连续数组 中控表 + 分段连续缓冲区 双向循环链表
尾部增删 O(1) O(1) O(1)
头部增删 O (n) 极慢 O (1) 极快 O (1) 极快
随机访问 [] 支持 O (1) 支持 O (1) 不支持
内存连续性 完全连续 分段连续 完全不连续
中间插入删除 O(n) O(n) O(1)
遍历效率 最高 中等 较低
迭代器失效 极易失效 仅当前块迭代器失效 仅被删除节点迭代器失效
对比维度 vector deque list
随机访问效率 **最高,**CPU 缓存命中率拉满 较高,跨块有开销 无法随机访问,只能遍历
顺序遍历速度 最快 次之 很慢,缓存失效严重
可用 std::sort 可以 可以 不可以
排序方式 std::sort / stable_sort std::sort / stable_sort 只能用成员函数 sort ()
排序算法 Introsort 内省排序(快) Introsort 内省排序 归并排序(慢、稳定)
排序稳定性 sort 不稳定 /stable_sort 稳定 sort 不稳定 /stable_sort 稳定 自带 sort 默认稳定
对比维度 vector deque list
内存要求 需要整块连续大内存 只需零散小块内存 仅需单个节点内存
超大数据容量 易分配失败(内存碎片) 适合海量大数据 适合海量大数据
内存占用开销 最小 中等 最大(每个节点 2 个指针)
内存释放 缩容需手动 shrink_to_fit 自动回收空闲内存块 销毁即释放节点
容器 最佳使用场景 不推荐场景
vector 1. 大量数据随机访问 / 遍历 2. 需要频繁排序 3. 只在尾部增删 4. 算法、普通业务存储 1. 频繁头插头删 2. 超大内存无连续空间 3. 频繁中间插入删除
deque 1. 头尾都要频繁增删 2. 需要随机访问 + 排序 3. 数据量大怕连续内存不足 1. 极致追求遍历访问速度 2. 频繁中间插入删除
list 1. 频繁任意位置增删 2. 要求迭代器永不失效 1. 大量遍历、随机访问 2. 需要高性能排序 3. 对内存开销敏感

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩 容时,也不需要搬移大量的元素,因此其 效率是比vector高 的。
与list比较,其底层是连续空间, 空间利用率比较高 ,不需要存储额外字段。
但是,deque有一个致命缺陷: 不适合遍历 ,因为在遍历时,deque的迭代器要频繁的去检测其 是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实 际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看 到的一个应用就是,STL用其作为stack和queue的底层数据结构。


六、迭代器失效规则

  1. deque 插入

    • 头尾插入:迭代器不会失效;
    • 中间 insert:所有迭代器失效。
  2. deque 删除

    • 头尾 pop:其他迭代器不失效;
    • 中间 erase:当前位置及后面迭代器失效。

对比 vector:一旦扩容,全部迭代器直接失效,deque 友好很多


模拟实现

queue

cpp 复制代码
namespace wxx1
{
	template<class T,class Container=deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_front();
		}
		size_t size()const
		{
			return _con.size;
		}
		bool empty() const
		{
			return _con.empty();
		}
		const T& front()const
		{
			return _con.front();
		}
		T& front()
		{
			return _con.front();
		 }
		const T& back()const
		{
			return _con.back();
		}
		T& back()
		{
			return _con.back();
		}
	private:
		Container _con;
	};
}

stack

cpp 复制代码
namespace wxx2
{
	// 适配器/配接器
	// 容器适配器
	template<class T, class Container = deque<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_back();
		}

		size_t size() const
		{
			return _con.size();
		}

		bool empty() const
		{
			return _con.empty();
		}

		const T& top() const
		{
			return _con.back();
		}

		T& top()
		{
			return _con.back();
		}

	private:
		Container _con;
	};
}

deque

cpp 复制代码
// 每块缓冲区大小
#define BLOCK_SIZE 8

template<typename T>
class MyDeque
{
private:
    // 中控:保存每个数据块的起始地址
    vector<T*> _map;
    // 当前首尾所在块、块内偏移
    int _startBlock;
    int _startPos;
    int _endBlock;
    int _endPos;
    // 元素总数
    size_t _size;

    // 申请一个数据块
    T* allocBlock()
    {
        return new T[BLOCK_SIZE];
    }

    // 扩容中控map
    void expandMap(bool front)
    {
        if (front)
            _map.insert(_map.begin(), allocBlock());
        else
            _map.push_back(allocBlock());
    }

public:
    MyDeque()
        : _startBlock(0), _startPos(0),
        _endBlock(0), _endPos(0), _size(0)
    {
        _map.push_back(allocBlock());
    }

    // 尾插
    void push_back(const T& val)
    {
        // 当前块满了,新开块
        if (_endPos == BLOCK_SIZE)
        {
            expandMap(false);
            _endBlock++;
            _endPos = 0;
        }
        _map[_endBlock][_endPos++] = val;
        _size++;
    }

    // 头插
    void push_front(const T& val)
    {
        // 当前头块到头了
        if (_startPos == 0)
        {
            expandMap(true);
            _startBlock++;  // 中控前面加一块,起始块后移
            _startPos = BLOCK_SIZE;
        }
        _map[_startBlock][--_startPos] = val;
        _size++;
    }

    // 尾删
    void pop_back()
    {
        if (empty()) return;
        _endPos--;
        _size--;
        // 块删空,跳到前一块
        if (_endPos == 0 && _endBlock > _startBlock)
        {
            _endBlock--;
            _endPos = BLOCK_SIZE;
        }
    }

    // 头删
    void pop_front()
    {
        if (empty()) return;
        _startPos++;
        _size--;
        // 当前块走完,跳到下一块
        if (_startPos == BLOCK_SIZE && _startBlock < _endBlock)
        {
            _startBlock++;
            _startPos = 0;
        }
    }

    // 随机访问 []
    T& operator[](size_t idx)
    {
        // 计算相对于起始的总偏移
        int total = _startPos + idx;
        int blockIdx = _startBlock + total / BLOCK_SIZE;
        int pos = total % BLOCK_SIZE;
        return _map[blockIdx][pos];
    }

    size_t size() const { return _size; }
    bool empty() const { return _size == 0; }

    // 清空
    void clear()
    {
        for (auto p : _map) delete[] p;
        _map.clear();
        _map.push_back(allocBlock());
        _startBlock = _endBlock = 0;
        _startPos = _endPos = 0;
        _size = 0;
    }

    // 析构
    ~MyDeque()
    {
        for (auto p : _map) delete[] p;
    }
};

相关推荐
ch.ju1 小时前
Java Programming Chapter 3——Subscript of the array
java·开发语言
雨落在了我的手上1 小时前
初识java(三):运算符
java·开发语言
爱吃香芋派OvO1 小时前
ComfyUI 视频创作实战手册:节点搭建 + 性能优化 + 批量生成
人工智能·算法·机器学习
爱喝水的鱼丶1 小时前
SAP-ABAP:ABAP Development Tools(ADT)安装配置学习分享教程(四篇连载)第四篇:ADT连接故障排查与环境迁移教程
运维·开发语言·数据库·学习·sap·abap
数智工坊1 小时前
【深度学习RL】A3C:异步强化学习的革命——用CPU打败GPU的深度RL算法
论文阅读·人工智能·深度学习·算法·transformer
灵智实验室1 小时前
PX4状态估计技术EKF2详解(三):EKF2 外部视觉融合——延迟后验状态与触发机制
算法·无人机·px 4
爱吃提升1 小时前
Yifan Hu(适合大规模数据)大数据算法
开发语言·算法·php
Xpower 171 小时前
从PHM到AI Agent-如何用OpenClaw构建设备健康诊断智能体
网络·人工智能·学习·算法
一只旭宝1 小时前
【C++入门精讲13】异常处理
c++