STL容器 ——stack和queue的模拟实现


前言: stack和queue两大容器的实现,还需要我们模拟实现它的构造,析构之类的吗?换言之:还需要手动的造轮子吗?我们可以复用其他的容器吗?stack要求先进后出,无非就是vector的尾插尾删;queue要求先进先出,无非就是list的头删尾插,这个不用vector的原因是:vector的头删效率较低,它得挪动数据,对吧。其实stack和queue底层复用得是deque,这个一会简单介绍一下。


1. 容器适配器

适配器是一种设计模式,它是将一种接口封装成我们所需得接口。stack和queue就是利用了容器适配器这种思想,它俩是对deque的封装使用。
可以看一下它俩实现的模板参数:
在这里插入图片描述
在这里插入图片描述
非常清楚的看到,模板参数默认传的是deque,当然我们也可以传其他的容器,比如传vector,list。如果传的容器不能够支持stack,queue的功能,在使用时会报错。

所以总结来说,stack和queue是利用了适配器这这设计模式进行的设计.。


2. stack的模拟实现

stack我们要求:只能从栈顶取出数据,从栈顶压入数据。
在这里插入图片描述
所以利用vector的尾插尾删就可以了。当然list也是满足需求的。

#include<vector>
namespace ly
{
	template<class T>
	class stack
	{
	public:
	    //压入数据
		void push(const T& val)
		{
			_stack.push_back(val);
		}
		//出栈
		void pop()
		{
			_stack.pop_back();
		}
		//求size
		size_t size() const
		{
			return _stack.size();
		}
		//栈顶数据
		const T& top() const
		{
			return _stack.back();
		}
		//判空
		bool empty() const 
		{
			return _stack.empty();
		}

	private:
	    //vector来实现stack,也可以是list
		std::vector<T> _stack;
	};
}


3. queue的模拟实现

queue要求:先进先出,也就是尾插,头出。
在这里插入图片描述
这个使用vector来实现就有点挫了,因为vector的头删复杂度是O(n),需要挪动数据,所以使用lis来实现是满足需求的。

#include<list>
namespace ly
{
	template<class T>
	class queue
	{
	public:
	    //插入数据
		void push(const T& val)
		{
			_queue.push_back(val);
		}
		//出队列
		void pop()
		{
		     //头出 
			_queue.pop_front();
		}
		// 求size
		size_t size() const
		{
			return _queue.size();
		}
		// 查看队列顶部数据
		const T& front() const
		{
			return _queue.front();
		}
		//判空
		bool empty() const
		{
			return _queue.empty();
		}

	private:
		std::list<T> _queue;
	};
}

4.deque的简易了解

deque设计的初衷是:既能支持随机访问,又能支持O(1)的插入操作,而且扩容也要求不能过于浪费空间。哇塞,想到这,这个deque也太牛了。简直完全可以去替代vector和list了,而且比它俩都牛。但是其实并不能替换vector或list。

deque是一个双端队列,也就是说它的头和尾都能够进行插入和删除。
在这里插入图片描述

(1) 它的物理空间是增么做到连续的呢?
其实也没有那么连续。它是由多个小的双端队列构成,每个双端队列的指针由一个中驱数组保存。

在这里插入图片描述
(2) 如何支持下标随机访问的?

每个双端队列的大小都一样,所以支持下标访问也就可以了,比如 a[10],每个双端队列的大小为4,用10去除以4,等于2,余数为2。也就是第二个双端队列的第二个元素。

(3) 如何扩容?

像vector每次扩容都是2倍扩容(Linux)或是1.5倍扩容(window)。假如我需求存1001个数据,它的空间是1000,为了存那一个数据,它有可能会扩容到2000,也就是说它浪费了999个空间。但deque这个时候的优势就显出来了,它每次只扩容一个双端队列,注意双端队列一般不大,所以一定程度上降低了空间的浪费。

(4) 缺点

deque有一个非常大的缺陷,就是它的遍历很麻烦。下标访问,每次都得计算后,才能找到对应的数据,还有它的迭代器需要不断的判断是否越界。这就导致遍历的效率较低了。所以线性结构一般都会用list或vector,而不是deque;为什么呢?因为线性结构应用场景下,多数需要进行多次的遍历。目前deque的应用,就是作为stack和queue的底层数据结构。

(5) deque作为stack和queue的底层数据结构的原因

  • stack和queue都不需要遍历。这就很符合deque的尿性
  • 在扩容方面,比vector要好
  • 在空间利用率方面,比list要好

还有一点我一直说是双端队列,但并不是队列,队列要求先进先出,而它是两端都可进可出。
它文档中写的是double ended queue,所以译成双端队列,也没毛病。
在这里插入图片描述


5. 有默认容器参数的stack和queue

上面的版本,我完成的比较粗暴,但是看到STL中的实现是有默认容器参数的。所以在对deque有一定了解的前提下,咱们来加上这个默认容器参数。

(1)stack的模拟实现

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

	private:
		container _stack;
	};
}

(2)queue的模拟实现

namespace ly
{
	template<class T,class container = deque<T>>
	class queue
	{
	public:
		void push(const T& val)
		{
			_queue.push_back(val);
		}
		void pop()
		{
			_queue.pop_front();
		}
		size_t size() const
		{
			return _queue.size();
		}
		const T& front() const
		{
			return _queue.front();
		}
		bool empty() const
		{
			return _queue.empty();
		}

	private:
		container _queue;
	};
}

所以我们在使用stack和queue时,可以这样调用:

stack<int,vector<int>> s;
stack<int>s1;

queue<int,list<int>> q;
queue<int>;

如果有想实验的小伙伴,我觉得queue也可以适配vector。那么给大家看看结果:

queue<int,vector<int>> ss;

在这里插入图片描述
很明显出错了,我还是用的STL中的queue来实验的。
它不支持头删除这个接口,如果我们自己非要用vector来模拟实现也是可以的,那就需要用到内部调用insert()。


结尾语:以上就是本篇内容,相信大家会加深对stack和queue的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

动名词

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

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

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

打赏作者

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

抵扣说明:

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

余额充值