C++stack,queue底层实现以及deque的详细介绍


前言

在本篇文章中,我们将会学到stack,queue的底层实现,我们通过本篇的学习,我们会发现,栈和队列的实现和vector,list等容器的实现会有很大差异。我们实现栈和队列是通过一种叫容器适配器的东西实现的。我们还将会学到deque,这是一种将list和vector结合的一种容器。我们来详细看一看吧!!!🌟🌟🌟

一、容器适配器

在介绍容器适配器之前,我们来想一下stack应该如何实现呢??

我们很清楚,栈是一种后进先出的结构,只能在一端进行数据的插入删除。
在c语言我们学习过,stack既可以用顺序表来实现,又可以用链表实现。
那么我们在C++中依然可以采用相同的方式来实现链式栈和顺序栈。

但是,我们发现在实现stack的接口中,很多都与顺序表的实现结构相似。
那麽我们把vector的一些接口进行封装,修改修改不就是我们stack的结构吗??

在C++官方库中,确实就是采用这种方法进行实现的。我们把这个叫做容器适配器

stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出

队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。

容器适配器是C++标准库中的一种数据结构,它可以将不同类型的容器(如vector、list、deque等)转换为另一种类型的容器。本质上,容器适配器是一种机制,能使某种容器的行为看起来像另外一种容器。这种转换提供了一种简单的方式来重新组织和访问数据,同时隐藏了底层容器的实现细节。容器适配器通常用于解决特定的问题或满足特定的需求。

二、stack的实现

我们现在已经知道实现stack采用容器适配器来实现,我们想实现一个链式栈,就采用list进行适配,我们想实现一个顺序栈,就采用vector进行适配。

vector适配缺点:容易造成空间浪费
list适配缺点:内存命中率较低

有没有一种两者兼有的结构呢??
我们来看一下stack库里的是如何实现的!!!
在这里插入图片描述
我们发现在进行适配时,默认采用了deque这个容器。

这其实是一个双端队列。尾插尾删效率很高,对于空间的浪费也没有那麽大,内存命中率也可以。我们先用这个实现一下,在后面我们再来详细看一下

template<class T,class con=deque<T>>
class stack
{
public:
	void push(const T&val)
	{
		_con.push_back(val);
	}
	void pop()
	{
		_con.pop_back();
	}
	size_t size()
	{
		return _con.size();
	}
	T& top()
	{
		return _con.back();
	}
	bool empty()
	{
		return _con.empty();
	}
private:
	con _con;
};

三、queue的实现

对于queue的实现我们想一下是否可以用vector和list实现

queue是先进先出,尾插头删,对于vector我们在库里面并没有头删的操作。
我们也可以用自己模拟实现的vector实现一个头删,但是我们知道对于头删,效率太低了
在这里插入图片描述

⭐️⭐️注意一下:适配器并不是一定要适配库里的,也可以适配自己模拟实现的,只要可以实现对应的需求就可以
对于list我们就可以了,但是list也有对应的缺陷,我们看一下库里的实现

在这里插入图片描述
我们发现依然默认采用deque的方式,我们同样的先用这个实现以下

	template<class T, class con = deque<T>>
	class queue
	{
	public:
		void push(const T& val)
		{
			_con.push_back(val);
		}
		void pop()
		{
			_con.pop_front();
		}
		size_t size()
		{
			return _con.size();
		}
		T& back()
		{
			return _con.back();
		}
		T& front()
		{
			return _con.front();
		}
		bool empty()
		{
			return _con.empty();
		}
	private:
		con _con;
	};


四、deque的介绍

stack和queue都是默认采用deque实现的,我们接下来看一下这个容器为什么比vector和list更适合适配。

deque是一个双端队列:两端开口,都可以进行插入删除,时间复杂度为O(1)。

我们来看一下它的底层

在这里插入图片描述

deque整个的物理空间并不是连续的,而是由一段段连续的物理空间结合而成,类似于一个二维数组。
我么给这一段段空间编号,方便接下来叙述。

我们来看一下头部插入和尾部插入

🔱🔱头插
✨ ✨ ✨ ✨ 如果一号空间不满,我们就直接在前面进行插入,假设我们插入1
在这里插入图片描述

✨ ✨ ✨ ✨ 如果一号空间已经满了,我们需要再开一块空间,把这块空间起始位置放在中控位置,再在新开的这块空间进行插入。
如果中控的位置也满了,就扩大中控位置,拷贝指针。
在这里插入图片描述

🔱🔱尾插
✨ ✨ ✨ ✨ 尾插也是同样的道理,尾部元素不满,继续插入。满了就再开一块空间继续插入

双端队列底层是一段假想的连续空间,实际是分段连续的,为了维护整体连续以及随机访问的假象,落在了deque迭代器上,迭代器设计就很复杂了

在这里插入图片描述
其中对于每段空间来说:
💗💗cur指向buff的某个位置
💗💗first指向buff的开始位置
💗💗last指向buff的结束位置
💗💗node指向存储buff中控位置

对于中控:
💗💗start指向第一个buff,cur指向这个buff第一个位置元素
💗💗finish指向最后一个buff,cur指向这个buff最后一个位置元素的下一个位置

我们可以看到deque有很多优势,头部插入删除,不需要挪动数据,弥补了vector的缺陷。同时底层空间是连续的,弥补了list的缺点,缓存利用率高。

那我们是不是可以考虑废除vector和list,z之后的学习都采用deque的结构呢??
显然是不行的,我们在数据结构已经学过了,就说明有存在的道理

deque有没有什么缺点呢??

🎁🎁下标的随机访问
既然deque兼备vector和list的特性,肯定具有下标的随机访问。
我们如果想要在deque中进行下标的随机访问,怎末做呢??
例如:deque中一共有100个元素,假设每个空间大小固定,都是10。我们想要访问第55个元素的位置。我们可以先用55/10,得到结果5,我们就知道我们要找的在第5块空间中,再用55%10,得到结果5,我们就可以找到这个位置进行访问了。
🎁🎁任意位置的插入删除
我们来看一这种场景,deque中有100个元素,我们想要在地55个元素的位置进行插入操作,我们如果每个空间大小固定,移动起来数据就相当麻烦。
那如果我们在这个空间的基础上进行扩大,不固定空间大小不就行了,这样确实可以实现,但是我们的下标随机访问就会收到影响。

我们只能空间时间选择一个,这就像鱼和熊掌不可兼得。
因此在实践中,deque的应用场景并不多,目前我们能看到的一个应用就是,STL用它作为stack和queue的底层数据结构。

既然deque有这麽大的缺点,我们为什么还要让它当作stack和queue的默认容器呢?

stack是一种后进先出的特殊线性数据结构,我们只要保证有尾插和尾删的相关操作就可以,比如vector,list都可以实现。queue是一种先进先出的特殊线性数据结构,只要保证有头删,尾插的相关操作就可以,比如list.

但是我们选择deque作为默认的容器:因为stack和queue完美的避开了这两大缺陷
🌝🌝stack和queue不需要随机访问,只能访问固定位置的数据。

🌝🌝stack在扩容时,deque比vector效率高,不需要挪动大量数据。queue元素增长时,deque效率高,内存使用率高。

总结

以上就是今天要讲的内容,本文仅仅详细介绍了C++stack和queue的模拟实现,以及deque的相关实现。希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lim 鹏哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值