【C++】_stack和_queue容器适配器、_deque

当别人都在关注你飞的有多高的时候,只有父母在关心你飞的累不累。💓💓💓

目录

  ✨说在前面

🍋知识点一:stack

•🌰1.stack介绍

•🌰2.stack的基本操作

🍋知识点二:queue

•🌰2.queue介绍

•🌰2.queue的基本操作

🍋知识点三:priority_queue

•🌰1.priority_queue介绍

•🌰2.priority_queue的基本使用

•🌰3.仿函数

🔥仿函数控制序列单调性

🔥仿函数在优先级队列中的应用

🍋知识点四:容器适配器

•🌰1.什么是适配器

•🌰2.stack和queue的底层结构

•🌰3.deque介绍

🔥deque的实现原理

🔥deque的缺陷

🔥选择deque的原因

 • ✨SumUp结语


  ✨说在前面

亲爱的读者们大家好!💖💖💖,我们又见面了,上一篇文章我给大家介绍了一下list的定义、常用接口以及模拟实现。如果大家没有掌握好相关的知识,上一篇篇文章讲解地很详细,可以再回去看看,复习一下,再进入今天的内容。

我们今天简单给大家讲解一下STL中的两大适配器——stack和queue。stack和queue分别对应C语言中的栈和队列,如果大家准备好了,那就接着往下看吧~

   👇👇👇
💘💘💘知识连线时刻(直接点击即可)

【C++】_string类字符串万字详细解析

【C++】_vector定义、_vector常用方法解析

【C++】_list常用方法解析及模拟实现

  🎉🎉🎉复习回顾🎉🎉🎉

         

 博主主页传送门:愿天垂怜的博客

 ​​​​​​

 

🍋知识点一:stack

•🌰1.stack介绍

stack是一个容器适配器,它提供了一种后进先出(LIFO, Last In First Out)的数据结构。stack只允许在容器的顶部进行元素的添加(push)和移除(pop)操作,以及访问顶部元素(top)的功能,但不提供遍历容器内部元素的功能

我们来查看一下文档中对stack的介绍:

注意:stack不属于容器,而是一种容器适配器。它看起来像是容器,但实际上它们是通过封装其他容器来工作的。

•🌰2.stack的基本操作

stack的使用接口如下:

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

它的接口非常简单,实现部分可以参考C语言的写法:C语言实现栈和队列

如果大家觉得不好理解,可以看我之前写的栈和队列的博客:【数据结构】栈和队列超详细讲解

接口文档如下:

stack::stack - C++ Reference (cplusplus.com)

stack::empty - C++ Reference (cplusplus.com)

stack::size - C++ Reference (cplusplus.com)

stack::top - C++ Reference (cplusplus.com)

stack::push - C++ Reference (cplusplus.com)

stack::emplace - C++ Reference (cplusplus.com)

stack::pop - C++ Reference (cplusplus.com)

stack::swap - C++ Reference (cplusplus.com)

 ​​​​​​

🍋知识点二:queue

•🌰2.queue介绍

queue是一种先进先出(FIFO, First In First Out)的数据结构。它允许在队尾添加元素(enqueue/push),在队首移除元素(dequeue/pop),以及查看队首元素(front)的值。与stack类似,queue也不提供遍历功能

我们来查看一下文档中对queue的介绍:

 注意:queue不属于容器,而是一种容器适配器。它看起来像是容器,但实际上它们是通过封装其他容器来工作的。

 

•🌰2.queue的基本操作

stack的使用接口如下:

函数说明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用

push()

在队尾将元素val入队列
pop()

将队头元素出队列

它的接口非常简单,实现部分可以参考C语言的写法:C语言实现栈和队列

如果大家觉得不好理解,可以看我之前写的栈和队列的博客:【数据结构】栈和队列超详细讲解

接口文档如下:

queue::queue - C++ Reference (cplusplus.com)

queue::empty - C++ Reference (cplusplus.com)

queue::size - C++ Reference (cplusplus.com)

queue::front - C++ Reference (cplusplus.com)

queue::back - C++ Reference (cplusplus.com)

queue::push - C++ Reference (cplusplus.com)

queue::emplace - C++ Reference (cplusplus.com)

queue::pop - C++ Reference (cplusplus.com)

queue::swap - C++ Reference (cplusplus.com)

 ​​​​​​

🍋知识点三:priority_queue

•🌰1.priority_queue介绍

学习完了队列,我们再来了解一下优先级队列:

C++中的优先级队列(Priority Queue)是一种特殊的队列,它的元素被赋予优先级,元素的出队顺序基于它们的优先级,而不是它们被加入队列的顺序。默认情况下,priority_queue使用最大堆(Max Heap)来实现,所以队列中最大的元素总是位于队列的顶部,因此这个元素会首先被移除(即出队)。

 

•🌰2.priority_queue的基本使用

优先级队列默认使用vector作为其底层存储数据的容器在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆

相同地,priority_queue也有如下接口:

函数声明接口说明
priority_queue()/priority_queue(first, last)
构造一个空的优先级队列
empty( )检测优先级队列是否为空,是返回true,否则返回false
top( )返回优先级队列中最大(最小元素),即堆顶元
push(x)在优先级队列中插入元素x
pop()
删除优先级队列中最大 ( 最小 ) 元素,即堆顶元

接口文档如下:

priority_queue::priority_queue - C++ Reference (cplusplus.com)

priority_queue::empty - C++ Reference (cplusplus.com)

priority_queue::size - C++ Reference (cplusplus.com)

priority_queue::top - C++ Reference (cplusplus.com)

priority_queue::push - C++ Reference (cplusplus.com)

priority_queue::emplace - C++ Reference (cplusplus.com)

priority_queue::pop - C++ Reference (cplusplus.com)

priority_queue::swap - C++ Reference (cplusplus.com)

 

•🌰3.仿函数

仿函数(Functors)是那些重载了()操作符的对象,它们的行为类似于函数。通过使用仿函数,你可以将函数的行为封装在对象内部,这样做的好处包括能够传递额外的状态信息、支持泛型编程(例如,与标准库算法一起使用时),以及更好的封装性。

🔥仿函数控制序列单调性

下面是一个仿函数示例,用于控制冒泡排序将原先序列按递增还是递减顺序排列:

//递增
template<class T>
class Less
{
public:
	bool operator()(const T& x, const T& y)
	{
		return x < y;
	}
};
//递减
template<class T>
class Greater
{
public:
	bool operator()(const T& x, const T& y)
	{
		return x > y;
	}
};
//冒泡排序
template<class Compare>
void BubbleSort(int* arr, int length, Compare com)
{
	assert(arr);
	int flag = 1;
	while (flag && length--)
	{
		flag = 0;
		for (int i = 0; i < length; i++)
		{
			//if (arr[i] > arr[i + 1])
			if (com(arr[i], arr[i + 1]))
			{
				swap(arr[i], arr[i + 1]);
				flag = 1;
			}
		}
	}
}
//打印数组元素
void printArr(int* arr, int length)
{
	for (size_t i = 0; i < length; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

int main()
{
	Less<int> LessFunc;
	Greater<int> GreaterFunc;
	int arr[] = { 5,23,789,12,8,1,86,12,7 };
	//升序
	BubbleSort(arr, 9, GreaterFunc);
	printArr(arr, 9);
	//降序
	BubbleSort(arr, 9, LessFunc);
	printArr(arr, 9);

	return 0;

在这个例子中,Less类和Greater类是仿函数,它重载了()操作符以比较两个整数。我们创建了Less或Greater的实例并将其作为第三个参数传递给BubbleSort函数,以指定排序的准则

结果如下:

1 5 7 8 12 12 23 86 789
789 86 23 12 12 8 7 5 1

 

🔥仿函数在优先级队列中的应用

利用仿函数,我们可以控制优先级队列的底层是大堆还是小堆,进而控制优先级队列的优先级。我们默认优先级队列的底层是大堆

它的实现用到了仿函数,可以参考我这里的实现:优先级队列模拟实现

优先级队列举例:

#include <iostream>
using namespace std;
#include <queue>

int main()
{
	priority_queue<int> pq;
	pq.push(3);
	pq.push(7);
	pq.push(1);
	pq.push(9);
	pq.push(10);
	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}

	return 0;
}

结果如下:

10 9 7 3 1

 ​​​​​​

🍋知识点四:容器适配器

•🌰1.什么是适配器

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

•🌰2.stackqueue的底层结构

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

我们来看看stack的模拟实现:

namespace stl_stack
{
	//Container适配转换出stack
	template<class T, class Container>
	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()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
}

再来看看stack的模拟实现:

很显然我们发现,这里容器的缺省值给的是deque<T>,这是个什么东西呢?继续往下看。

•🌰3.deque介绍

🔥deque的实现原理

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

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

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

 

🔥deque的缺陷

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

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

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

我们可以用下面的代码来测试vector和deque的[]利率:

//deque与vector[]的效率对比
void test_op1()
{
	srand(time(0));
	const int N = 1000000;
	deque<int> dq;
	vector<int> v;
	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		dq.push_back(e);
		v.push_back(e);
	}
	int begin1 = clock();
	sort(dq.begin(), dq.end());
	int end1 = clock();

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

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

void test_op2()
{
	srand(time(0));
	const int N = 1000000;
	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("deque sort:%d\n", end1 - begin1);
	printf("deque copy vector sort, copy back deque:%d\n", end2 - begin2);
}

int main()
{
	test_op1();
	test_op2();

	return 0;
}

realse且x86结果如下:

deque:117
vector:66
deque sort:104
deque copy vector sort, copy back deque:59

 基本上相差两倍左右。

 

🔥选择deque的原因

那么问题来了,为什么选择deque作为stack和queue的底层容器?

stack是一种后进先出的特殊线性数据结构,因此只要具有【push_back】和【pop_back】操作的线性结构,都可以作为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的优点,而完美的避开了其缺陷。

 ​

 • ✨SumUp结语

到这里本篇文章的内容就结束了,本节介绍了C++中_stack_queue的相关知识。这里的内容虽然很熟悉了,而且也很简单。但是也希望大家能够认真学习,打好基础,迎接接下来的挑战,期待大家继续捧场~💖💖💖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值