【C++强基篇】学习C++就看这篇--->STL之stack、queue、deque使用及实现

主页:HABUO🍁主页:HABUO

🍁C++入门到精通专栏🍁

🍁如果再也不能见到你,祝你早安,午安,晚安🍁


目录

📕一、stack的介绍 

📕二、stack的使用   

📂 头文件与命名空间

✨2.1 构造与初始化  

✨2.2 元素访问

✨2.3 修改操作

✨2.4 容量查询  

✨2.5 关键特性与注意事

2.5.1. 底层容器

2.5.2. 时间复杂度

2.5.3. 注意事项 

📕三、stack的简单模拟实现    

📕四、queue的介绍  

📕五、queue的使用    

📂 头文件与命名空间 

✨5.1 构造与初始化   

✨5.2 元素访问 

 ✨5.3 修改操作

 ✨5.4 容量查询

 ✨5.5 关键特性与注意事 

5.5.1. 底层容器要求

5.5.2. 时间复杂度

5.5.3. 注意事项

📕六、queue的简单模拟实现   

📕七、deque(双端队列)(了解)

✨7.1 基本操作与成员函数   

✨7.2 关键特性与注意事项 

7.2.1. 底层数据结构(分段数组)

7.2.2. 时间复杂度对比

7.2.3. 迭代器特性

✨7.3 deque 与其他容器对比 

 📕八、总结


前言: 

上篇博客我们了解了STL中的list类,本篇博客我们继续对STL中的组件进行学习,这篇博客与前几次的不一样,我们知道STL中有六大组件,,前面我们所学习的都是其中的容器,今天我们要学习其中的适配器stack、queue,学习思路与前面的string和vector、list大致是一样的,第一步就是:是什么怎么用,第二步就是我们能不能自己实现一个简单的vector(即为了了解它的底层原理),经过这两步的学习之后,应对绝大多数的场景已经足够使用。stack、queue学起来是比较简单的,只是有一点是与前面是不同的,它们引入了一个适配器的概念,不过总体而言呢,还是相对比较简单,希望大家有所收获!

📕一、stack的介绍 

std::stack 是 C++ 标准模板库提供的栈容器适配器,实现了后进先出(LIFO)的数据结构。它基于其他容器(如 deque 或 list)实现,提供受限的栈操作接口。 

🌟 核心特性

  • 后进先出(LIFO):最后压入的元素最先弹出

  • 容器适配器:基于底层容器实现(默认 std::deque

  • 受限接口:只暴露栈操作相关的方法

  • 无迭代器:不支持元素遍历

📕二、stack的使用   

📂 头文件与命名空间

#include <stack>
using namespace std;  // 或显式使用 std::stack

类似于string与vector和list,我们学习使用主要针对的就是,构造与初始化、元素访问、修改操作、容量查询。由于stack与queue使用起来很简单,总体来说总共有下面这些接口。下面我们来一一介绍。  

函数说明接口说明
stack()(重点)构造空的栈
empty()(重点)检测 stack 是否为空
size()(重点)返回 stack 中元素的个数
top()(重点)返回栈顶元素的引用
push(val)(重点)将元素 val 压入 stack 中
pop()(重点)将 stack 中尾部的元素弹出

✨2.1 构造与初始化  

std::stack<int> stk1;           // 空栈(默认底层容器 deque)
std::stack<int, std::list<int>> stk2; // 指定底层容器为 list

std::deque<int> deq {1, 2, 3};
std::stack<int> stk3(deq);      // 用已有 deque 初始化

deque是什么下面进行介绍 

✨2.2 元素访问

stk1.push(10);         // 压栈
int top_val = stk1.top(); // 访问栈顶元素(不弹出)
// 注意:空栈调用 top() 是未定义行为

✨2.3 修改操作

stk1.push(20);      // 压入元素
stk1.pop();         // 弹出栈顶元素(不返回该元素)
// 组合操作:获取并弹出
int val = stk1.top();
stk1.pop();

✨2.4 容量查询  

bool empty = stk1.empty();  // 判断栈是否为空
size_t size = stk1.size();  // 返回元素数量

✨2.5 关键特性与注意事

2.5.1. 底层容器

底层容器特性适用场景
std::deque(默认)内存分段连续,高效两端操作通用场景
std::vector内存连续,随机访问快需要连续内存时
std::list任意位置插入高效大对象存储
// 使用 vector 作为底层容器
#include <vector>
std::stack<int, std::vector<int>> vec_stack;

2.5.2. 时间复杂度

操作时间复杂度说明
push()O(1)压栈
pop()O(1)弹栈
top()O(1)访问栈顶
size()O(1)大小查询

2.5.3. 注意事项 

  • 空栈检查:调用 top() 或 pop() 前必须检查 empty()

  • 无清空方法:没有 clear(),需手动弹出

    while (!stk1.empty()) stk1.pop();
  • 无迭代器:不能遍历栈内容(违反栈设计原则)

  • 容器选择:大对象建议使用 list 作为底层容器

学习了上面的知识可以练习下面几道题试试手:

最小栈 

栈的压入、弹出序列 

逆波兰表达式求值 

📕三、stack的简单模拟实现    

先看实现代码:

namespace hab
{
	template<class T, class Container>//这就是适配器的原理就是用之前所学的容器进行转换
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_back();
		}
		size_t size()
		{
			return _con.size();
		}
		bool empty()
		{
			return _con.empty();
		}
		T& top()
		{
			return _con.back();
		}
	private:
		Container _con;
	};
}

适配器解析

template<class T, class Container>

所谓适配器就是用一个模板参数,无论你用什么容器只要适合我,你传过来,编译器自动的去处理,所谓适合就是那些接口是适用的,仅此而已。

📕四、queue的介绍  

std::queue 是 C++ 标准模板库提供的队列容器适配器,实现了先进先出(FIFO)的数据结构。基于底层容器(默认 deque)实现。

🌟 核心特性

  • 先进先出(FIFO):最先插入的元素最先移除

  • 容器适配器:基于底层容器实现(默认 std::deque

  • 受限接口:只暴露队列操作相关的方法

  • 无迭代器:不支持元素遍历

📕五、queue的使用    

📂 头文件与命名空间 

#include <queue>  // 注意:与 stack 不同头文件
using namespace std;  // 或显式使用 std::queue

与stack极其相似,主要使用:构造与初始化、元素访问、修改操作、容量查询。由于stack与queue使用起来很简单,总体来说总共有下面这些接口。下面我们来一一介绍。   

函数声明接口说明
queue()(重点)构造空的队列
empty()(重点)检测队列是否为空,是返回 true,否则返回 false
size()(重点)返回队列中有效元素的个数
front()(重点)返回队头元素的引用
back()(重点)返回队尾元素的引用
push(val)(重点)在队尾将元素 val 入队列
pop()(重点)将队头元素出队列

✨5.1 构造与初始化   

std::queue<int> que1;            // 空队列(默认底层容器 deque)
std::queue<int, std::list<int>> que2; // 指定底层容器为 list

std::deque<int> deq {1, 2, 3};
std::queue<int> que3(deq);       // 用已有 deque 初始化

✨5.2 元素访问 

que1.push(10);          // 入队
int front_val = que1.front(); // 访问队首元素
int back_val = que1.back();   // 访问队尾元素

 ✨5.3 修改操作

que1.push(20);      // 入队(队尾)
que1.pop();         // 出队(队首,不返回元素)

 ✨5.4 容量查询

bool empty = que1.empty();  // 判断队列是否为空
size_t size = que1.size();  // 返回元素数量

 ✨5.5 关键特性与注意事 

5.5.1. 底层容器要求

操作所需容器方法
front()front()
back()back()
push()push_back()
pop()pop_front()

有效底层容器

  • std::deque(默认)

  • std::list

  • ❌ std::vector(缺少 pop_front()

// 使用 list 作为底层容器
#include <list>
std::queue<int, std::list<int>> list_queue;

5.5.2. 时间复杂度

操作时间复杂度说明
push()O(1)入队
pop()O(1)出队
front()O(1)访问队首
back()O(1)访问队尾

5.5.3. 注意事项

  • 空队列检查:调用 front()back() 或 pop() 前必须检查 empty()

  • 无清空方法:需手动弹出元素清空队列

    while (!que1.empty()) que1.pop();
  • 无中间访问:只能访问首尾元素

  • 线程安全:STL 容器本身非线程安全,需外部同步

📕六、queue的简单模拟实现   

namespace hab
{
	template<class T, class Container>//在队列中实现的话用list,因为vector底层没有实现pop_front()这样的接口
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_front();
		}
		size_t size()
		{
			return _con.size();
		}
		bool empty()
		{
			return _con.empty();
		}
		T& front()
		{
			return _con.front();
		}
		T& back()
		{
			return _con.back();
		}
	private:
		Container _con;
	};
}

💎 stack 与 queue 对比总结

特性std::stackstd::queue
数据结构LIFO(后进先出)FIFO(先进先出)
核心操作push()pop()top()push()pop()front()back()
默认容器std::dequestd::deque
访问范围仅栈顶队首和队尾
典型应用递归/DFS/撤销操作BFS/任务队列/缓冲管理
头文件<stack><queue>

📕七、deque(双端队列)(了解)

上面我们提到了stack与queue它们底层是通过适配器实现的,对于stack,vector、list都可以,对于queue只能用list,事实上,STL源码当中两者都没用,而是用了另外的一个容器dequedeque是一个既吸收了vector随机访问的优点,又吸收了list插入删除效率高的优点,可以说是一个集大成者,但是相信大家看到上面我们标注了了解,为什么?因为C++是一个极其注重效率的语言,虽然deque非常好,但是细分情况下,它是比不过vector和list的,例如随机访问,它的效率是不如vector的。大家可以通过下述代码进行试验:

void test_deque()
{
	deque<int> d;
	vector<int> v;
	const int n = 100000;
	srand(time(0));
	for (size_t i = 0; i < n; ++i)
	{
		int x = rand();
		d.push_back(x);
		v.push_back(x);
	}
	size_t begin1 = clock();
	sort(d.begin(), d.end());
	size_t end1 = clock();

	size_t begin2 = clock();
	sort(v.begin(), v.end());
	size_t end2 = clock();
	cout <<  end1 - begin1 << endl;
	cout <<  end2 - begin2 << endl;
}

std::deque(Double-Ended Queue)是 C++ 标准模板库提供的一种动态数组容器,支持在两端高效插入和删除元素。它结合了 vector 的随机访问能力和 list 的两端操作效率,是 STL 中功能最全面的序列容器之一。

🌟 核心特性

  • 双端高效操作:在头尾插入/删除元素均为 O(1) 时间复杂度

  • 随机访问:支持下标操作符 [] 和 at(),访问效率 O(1)

  • 分段连续存储:数据存储在多个固定大小的块中(指针数组管理)

  • 动态扩容:自动管理内存,扩容时不需要复制所有元素

  • 迭代器复杂:随机访问迭代器,但比 vector 迭代器更复杂

✨7.1 基本操作与成员函数   

  •  构造与初始化
std::deque<int> dq1;             // 空 deque
std::deque<int> dq2(5, 100);     // 5个值为100的元素
std::deque<int> dq3 = {1, 2, 3}; // 初始化列表 (C++11)
std::deque<int> dq4(dq3.begin(), dq3.end()); // 迭代器范围构造
std::deque<int> dq5(dq4);        // 拷贝构造
  • 元素访问
// 随机访问
int first = dq3[0];         // 下标访问(无边界检查)
int second = dq3.at(1);     // 带边界检查(越界抛异常)
int front = dq3.front();    // 首元素
int back = dq3.back();      // 尾元素

// 迭代器访问
auto it = dq3.begin() + 2;  // 随机访问迭代器
int val = *it;
  •  修改操作
// 两端操作
dq3.push_front(0);     // 头部插入
dq3.push_back(4);      // 尾部插入
dq3.pop_front();       // 删除头部
dq3.pop_back();        // 删除尾部

// 中间操作
auto pos = dq3.begin() + 2;
dq3.insert(pos, 10);   // 指定位置插入(O(n))
dq3.erase(pos);        // 删除指定位置(O(n))

// 批量操作
dq3.resize(10);        // 调整大小
dq3.assign(5, 42);     // 重新赋值
dq3.clear();           // 清空所有元素
  • 容量查询 
bool empty = dq3.empty();  // 是否为空
size_t size = dq3.size();  // 元素数量
size_t cap = dq3.max_size(); // 理论最大容量
// 注意:deque 没有 capacity() 和 reserve() 方法

✨7.2 关键特性与注意事项 

7.2.1. 底层数据结构(分段数组)

  • 分块存储:元素存储在多个固定大小的内存块中

  • 映射表:使用指针数组(映射表)管理数据块

  • 扩容机制

    • 添加元素时只需分配新块,无需移动现有元素

    • 扩容成本低于 vector(不需要整体复制)

可以发现,它的底层是通过一个中控数组(指针数组),来控制多个数组来实现的,也就是说指针数组里存放着这些数组块的地址,当要访问某个数据的时候先找到在哪个数组,在去数组里找到具体的数据。而且它的迭代器也是很复杂,如下面所述: 

7.2.2. 时间复杂度对比

操作dequevectorlist
头/尾插入删除O(1)尾O(1)/头O(n)O(1)
中间插入删除O(n)O(n)O(1)
随机访问O(1)O(1)O(n)
迭代器自增O(1)O(1)O(1)
内存局部性中等

7.2.3. 迭代器特性

  • 随机访问迭代器:支持 it + n 等操作

  • 失效规则

    • 头尾插入:通常不失效(除非引起重映射)

    • 中间插入:可能失效所有迭代器

    • 删除操作:被删位置之后的迭代器失效

  • 复杂结构:迭代器包含多个指针(当前块位置/块指针/映射表指针)

它的迭代器是通过四个指针来相互配合进行随机访问的,仅作了解即可。 

✨7.3 deque 与其他容器对比 

特性std::dequestd::vectorstd::list
存储结构分段连续单块连续离散节点
随机访问O(1)O(1)O(n)
头部插入O(1)O(n)O(1)
尾部插入O(1)O(1)*O(1)
中间插入O(n)O(n)O(1)
迭代器失效复杂(部分失效)频繁(扩容时全失效)稳定(仅删除元素失效)
内存开销中(指针+可能碎片)低(仅容量冗余)高(每个节点两指针)
缓存友好

注:vector 尾部插入均摊 O(1),但扩容时成本高 

std::deque 是 STL 中最灵活的序列容器,核心优势在于:

  1. 双端 O(1) 操作:完美实现队列和栈

  2. 高效随机访问接近 vector 的性能,注意是接近,但是相对于vector还是逊色点

  3. 智能扩容机制:避免 vector 的整体复制问题

适用场景:

  • 需要同时支持随机访问和双端操作的序列

  • 滑动窗口算法

  • 作为 stack 和 queue 的底层容器

  • 数据量较大且扩容成本敏感的应用

 对于deque,大家知道它可以弥补vector、list的缺陷,是一个全能的容器,并且作为stack、queue底层适配器,简单了解其中的使用即可。


 📕八、总结

本篇博客我们了解学习了stack、queue、deque。 std::stack 与 std::queue 皆为容器适配器,默认基于 deque,分别提供 LIFO 与 FIFO 语义。stack 仅暴露 push、pop、top、empty、size,复杂度全 O(1),底层可换 vector/list;queue 增 front/back,同理复杂度 O(1),底层需支持 pop_front。二者均不可遍历,仅接口差异。简单模拟:内部保存 Container _con,转调其 push_back/pop_back 或 pop_front 即可。deque 是双端队列,分段数组,头尾插删 O(1),随机访问 O(1),中间插删 O(n),迭代器失效规则复杂,比 vector 头插快,比 list 缓存友好,常用于栈、队列的默认底层。希望大家有所收获!


评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值