首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++STL :stack && queue (二) 】stack 与 queue 的模拟实现与双端队列探秘

【C++STL :stack && queue (二) 】stack 与 queue 的模拟实现与双端队列探秘

作者头像
艾莉丝努力练剑
发布2025-11-18 13:29:21
发布2025-11-18 13:29:21
1150
举报
文章被收录于专栏:C / C++C / C++

C++的两个参考文档

老朋友(非官方文档):cplusplus 官方文档(同步更新):cppreference

stack容器文档链接:stack queue容器文档链接:queue

3 ~> stack模拟实现

在C++ STL中,stack(栈)是一种重要的容器适配器,遵循后进先出(LIFO)原则。本文将深入探讨stack的两种实现方式:手动内存管理和容器适配器模式。

3.1 第一种实现:手动管理内存的栈

3.2 第二种实现:容器适配器模式的栈

3.2.1 向栈顶添加元素(压栈)
3.2.2 从栈顶移除元素(弹栈)
3.2.3 获取栈顶元素的引用
3.2.4 返回栈中元素的数量
3.2.5 检查栈是否为空,调用底层容器的empty方法
3.2.6 底层容器对象,实际存储数据
3.2.7 基于现有容器实现的栈数据结构

3.3 运行

3.3.1 代码演示

代码演示如下——

代码语言:javascript
复制
// stack
#include"stack.h"

int main()
{
	//jqj::stack<int, vector<int>> st; // 使用vector作为底层容器:顺序表数组
	//jqj::stack<int, list<int>> st;   // 使用list作为底层容器:链式数组
	jqj::stack<int> st;				// 使用默认容器(deque)
	// 栈操作
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);

	// 遍历并输出栈内容(后进先出)
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}
	cout << endl;

	return 0;
}
3.3.2 运行结果

运行结果如下图所示——


4 ~> queue模拟实现

在C++标准模板库(STL)中,queue(队列)是一种重要的容器适配器,它遵循先进先出(FIFO)的原则。本文将深入解析queue的底层实现原理,并展示如何基于现有容器构建队列数据结构。

模板参数:template<class T, class Container = deque<T>>

关于底层容器,默认使用deque<T>,也可替换为list<T>,这个设计就是为了能够复用现有容器的功能,提供特定的队列接口,极大方便程序员写代码。

4.1 向队列尾部添加元素

4.2 从队列头部移除元素

4.3 获取队列头部元素的引用

4.4 获取队列尾部元素的引用

4.5 返回队列中元素的数量

4.6 检查队列是否为空

4.7 容器适配器——基于现有容器实现的队列数据结构

4.8 运行

4.8.1 代码演示

代码演示如下——

代码语言:javascript
复制
// queue
#include"queue.h"

int main()
{
	// 使用不同底层容器实现的自定义队列
	jqj::queue<int> q;				// 使用默认容器(deque)
	//jqj::queue<int, vector<int>> q; // 使用vector作为底层容器:顺序表数组
	//jqj::queue<int, list<int>> q;   // 使用list作为底层容器:链式数组

	// 队列操作
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);

	// 遍历并输出队列内容(先进先出)
	while (!q.empty())
	{
		cout << q.front() << " "; // 输出:1 2 3 4
		q.pop();
	}
	cout << endl;

	return 0;
}
4.8.2 运行结果

运行结果如下图所示——


5 ~> 容器适配器

5.1 什么是适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设 计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。

5.2 STL标准库中stack和queue的底层结构

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque,比如:

后面我们会介绍双端队列(deque)这个容器适配器。


6 ~> vector 和 list 的优缺点

6.1 vector的优缺点

6.1.1 vector的优点

1、支持快速地下标随机访问; 2、尾插尾删效率很高; 3、CPU高速缓存访问(包含读 / 写)命中率高——数据访问效率高。

100万个数据的数组排序,两者的运行时间差不多;100万个数组排序(快速排序)和100万个链表排序(归并排序),两者运行时间有一定差异。

GPU是也是一种特殊的CPU。

6.1.2 vector的缺点

1、头部 / 中间位置插入删除效率很低; 2、插入存在扩容,扩容也有一定性能消耗((1)消耗不大;(2)越扩越慢——越扩越大,这个“慢”不是指性能,而是频率)、扩容也存在一定空间浪费。

如果知道要多大空间,用reserve可以一定程度上缓解这个问题。

6.2 list 的优缺点

6.2.1 list 的优点

1、任意位置插入、删除效率都很高(O(1)); 2、插入不存在扩容,按需申请释放内存。

注意:使用迭代器查找位置属于查找的消耗,任意位置插入、删除效率是很高的。

6.2.2 list 的缺点

1、不支持快速地下标随机访问; 2、CPU高速缓存访问(包含读 / 写)命中率高——数据访问效率高。

6.2.3 总结

6.3 基于CPU高速缓存访问命中率低的拓展

关于缓存,这里推荐我们的老前辈陈皓老师的一篇文章——与程序员相关的CPU缓存知识

前面大家可以看看,写得很好!后面偏向【计算机组成原理】相关的内容,大家尽力而为!

上图中下面的图见陈皓老师的与程序员相关的CPU缓存知识一文。


7 ~> 了解双端队列

7.1 deque的原理介绍

deque(双端队列):是一种双开口的“连续“空间的数据结构,双开口的含义是:可以在头尾两端 进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与 list比较,空间利用率比较高。

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个 动态的二维数组,其底层结构如下图所示:

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问 的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

那deque是如何借助其迭代器维护其假想连续的结构呢?

7.2 设计出一个兼容他们优点的数据结构呢?deque(?)

7.3 deque的缺陷

能否用deque去替代vector 和 list的功能?不能。 ——初衷是好的,但是deque是不那么成功的。

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在 容时,也不需要搬移大量的元素,因此其效率是必vector高的。

与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其 是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实 际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看 到的一个应用就是,STL用其作为stack和queue的底层数据结构。

运行一下——

7.4 为什么选择deque作为stack和queue的底层默认容器

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back(和pop_backo操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

1、stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。 2、在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。

deque结合了两者的优点,而完美的避开了其缺陷。

7.5 面试题

vector优缺点,list优缺点,折中方案——deque。 能不能替代?不能,为什么?

——就是上面的内容啦!


本文完整代码演示

底层实现完整展示

stack.h:
代码语言:javascript
复制
#pragma once
#include<vector>
#include<list>
#include<deque>

namespace jqj
{
	//template<class T>
	//class stack
	//{
	//	// ...
	//private:
	//	T* _a;
	//	size_t _top;
	//	size_t _capacity;
	//};

	// deque:双端队列
	template<class T,class Container = deque<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

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

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

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

		bool empty() const
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
}
queue.h:
代码语言:javascript
复制
#include<vector>
#include<list>
#include<deque>

namespace jqj
{
	// 容器适配器
	// deque:双端队列
	template<class T, class Container = deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_front(); // 队头,先进先出后进后出
		}

		const T& front()
		{
			return _con.front();
		}

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

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

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

	private:
		Container _con;
	};
}
Test.cpp:
代码语言:javascript
复制
#define  _CRT_SECURE_NO_WARNINGS  1

// stack 和 queue的使用:构造、增删
#include<iostream>
#include<stack>
#include<queue>
using namespace std;

int main()
{
	// 栈(stack)的使用演示
	stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.emplace(4); // emplace是C++11的高效插入

	// 遍历并清空栈 (LIFO: 后进先出)
	while (!st.empty())
	{
		cout << st.top() << " "; // 输出栈顶元素:4 3 2 1
		st.pop(); // 弹出栈顶元素
	}
	cout << endl;

	// 队列(queue)的使用演示
	queue<int> q;
	q.push(1);
	q.push(2);
	q.push(3);
	q.emplace(4); // emplace是C++11的高效插入

	while (!q.empty())
	{
		cout << q.front() << " "; // 输出队头元素
		q.pop();				// 出队
	}

	return 0;
}

// stack
#include"stack.h"

int main()
{
	//jqj::stack<int, vector<int>> st; // 使用vector作为底层容器:顺序表数组
	//jqj::stack<int, list<int>> st;   // 使用list作为底层容器:链式数组
	jqj::stack<int> st;				// 使用默认容器(deque)
	// 栈操作
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);

	// 遍历并输出栈内容(后进先出)
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}
	cout << endl;

	return 0;
}

// queue
#include"queue.h"

int main()
{
	// 使用不同底层容器实现的自定义队列
	jqj::queue<int> q;				// 使用默认容器(deque)
	//jqj::queue<int, vector<int>> q; // 使用vector作为底层容器:顺序表数组
	//jqj::queue<int, list<int>> q;   // 使用list作为底层容器:链式数组

	// 队列操作
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);

	// 遍历并输出队列内容(先进先出)
	while (!q.empty())
	{
		cout << q.front() << " "; // 输出:1 2 3 4
		q.pop();
	}
	cout << endl;

	return 0;
}

Deque与vector && list效率对比代码演示

Test.cpp:
代码语言:javascript
复制
void Test_op1()
{
	srand(time(0));
	const int N = 10000000;

	deque<int> dq;
	vector<int> v;
	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		v.push_back(e);
		dq.push_back(e);
	}

	int begin1 = clock();
	sort(v.begin(), v.end());
	int end1 = clock();

	int begin2 = clock();
	sort(dq.begin(), dq.end());
	int end2 = clock();

	printf("vector:%d\n", end1 - begin1);
	printf("deque:%d\n", end2 - begin2);
}

void Test_op2()
{
	srand(time(0));
	const int N = 10000000;

	deque<int> dq1;
	deque<int> dq2;

	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		dq1.push_back(e);
		dq2.push_back(e);
	}

	int begin1 = clock();
	sort(dq1.begin(), dq1.end());
	int end1 = clock();

	int begin2 = clock();
	// 拷贝到vector
	vector<int> v(dq2.begin(), dq2.end());
	sort(v.begin(), v.end());
	dq2.assign(v.begin(), v.end());
	int end2 = clock();

	printf("vector:%d\n", end1 - begin1);
	printf("deque copy vector sort,sort back deque:%d\n", end2 - begin2);
}

int main()
{
	Test_op2();

	return 0;
}
效率对比(release版本运行)

结尾

往期回顾:

【C++STL :stack && queue (一) 】stack与queue全解析|深入使用,附高频算法题详解

结语:都看到这里啦!那请大佬不要忘记给博主来个“一键四连”哦!

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • C++的两个参考文档
  • 3 ~> stack模拟实现
    • 3.1 第一种实现:手动管理内存的栈
    • 3.2 第二种实现:容器适配器模式的栈
      • 3.2.1 向栈顶添加元素(压栈)
      • 3.2.2 从栈顶移除元素(弹栈)
      • 3.2.3 获取栈顶元素的引用
      • 3.2.4 返回栈中元素的数量
      • 3.2.5 检查栈是否为空,调用底层容器的empty方法
      • 3.2.6 底层容器对象,实际存储数据
      • 3.2.7 基于现有容器实现的栈数据结构
    • 3.3 运行
      • 3.3.1 代码演示
      • 3.3.2 运行结果
  • 4 ~> queue模拟实现
    • 4.1 向队列尾部添加元素
    • 4.2 从队列头部移除元素
    • 4.3 获取队列头部元素的引用
    • 4.4 获取队列尾部元素的引用
    • 4.5 返回队列中元素的数量
    • 4.6 检查队列是否为空
    • 4.7 容器适配器——基于现有容器实现的队列数据结构
    • 4.8 运行
      • 4.8.1 代码演示
      • 4.8.2 运行结果
  • 5 ~> 容器适配器
    • 5.1 什么是适配器
    • 5.2 STL标准库中stack和queue的底层结构
  • 6 ~> vector 和 list 的优缺点
    • 6.1 vector的优缺点
      • 6.1.1 vector的优点
      • 6.1.2 vector的缺点
    • 6.2 list 的优缺点
      • 6.2.1 list 的优点
      • 6.2.2 list 的缺点
      • 6.2.3 总结
    • 6.3 基于CPU高速缓存访问命中率低的拓展
  • 7 ~> 了解双端队列
    • 7.1 deque的原理介绍
    • 7.2 设计出一个兼容他们优点的数据结构呢?deque(?)
    • 7.3 deque的缺陷
    • 7.4 为什么选择deque作为stack和queue的底层默认容器
  • 本文完整代码演示
    • 底层实现完整展示
      • stack.h:
      • queue.h:
      • Test.cpp:
    • Deque与vector && list效率对比代码演示
      • Test.cpp:
      • 效率对比(release版本运行)
  • 结尾
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档