【C++】STL中stack和queue的模拟实现&&deque讲解

往期回顾

链接 内容
string模拟实现 STL中string的模拟实现
vector模拟实现 STL中vector的模实现
list模拟实现 list的模拟实现下星期补上来

文章目录

  • stack和queue的认识
  • stack和queue的底层
  • deque的原理
  • deque的优缺点
    • 选择deque的原因
  • stack和queue的模拟实现

stack和queue的认识

stack和queue相必大家并不陌生,那么STL是如何实现的呢?它们俩虽然可以存放元素,但被归列在Container adaptors容器适配器中,先来认识一下什么是适配器
在这里插入图片描述

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。
简单来说就是将别人的东西贴上自己的标签变成自己的东西(套娃)
【C++】STL中stack和queue的模拟实现&&deque讲解_第1张图片

stack和queue的底层

既然stack和queue是适配器,那么它们俩的底层就是对其他容器的接口进行封装变为自己的接口。

  • stack是后进先出LIFO,尾插尾删list和vector都可以实现
  • queue是先进先出FIFO,尾插头删用list更合适

那么stack的底层是不是这样呢

namespace k
{	
	template<class T>
	class stack
	{
	public:
		void push(const T& x);
		void pop();
		const T& top() const;
	private:
		vector<T> _v;
	};
}

或是这样,给个缺省参数vector或是list

namespace king
{	
	template <class T, class Container = vector<T>>
	class stack
	{
	public:
		void push(const T& x);
		void pop();
		const T& top() const;
	private:
		Container<T> _con;
	};
}

stack的底层确实是这样的,但是缺省参数不是vector也不是list而是deque(双端队列)

template <class T, class Container = deque<T> > class stack;

deque的原理

deque的出现实际上是为了融合list和vector的优点,从而代替它们,显然vector和list并没有被代替

  • vector的优点:支持下标随机访问,尾插尾删效率高,CPU高速缓存命中率高
  • vector的缺点:不适合头插头删,时间复杂度O(N),可能存在空间浪费,需要异地扩容时效率低
  • list的优点:按需申请空间,不存在空间浪费,任意位置插入删除的时间复杂度O(1)
  • list的缺点:不支持下标随机访问,频繁的申请空间

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高,不需要存储额外字段。
【C++】STL中stack和queue的模拟实现&&deque讲解_第2张图片

但deque并不是真正的连续空间(空间连续了但没完全连续),而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:
【C++】STL中stack和queue的模拟实现&&deque讲解_第3张图片当然为了能够支持随机访问,deque的iterator迭代器就设计的十分复杂了
deque的迭代器封装了四个指针,first和last用来指向buffer的头尾,cur用来遍历buffer,然后将node指向中控数组对应位置
【C++】STL中stack和queue的模拟实现&&deque讲解_第4张图片

最后还有指向头尾buffer的start和first
【C++】STL中stack和queue的模拟实现&&deque讲解_第5张图片

deque的优缺点

vector的随机访问list的任意位置插入删除O(1)的时间复杂度做到了极致,而deque的中间位置的插入删除和随机访问的效率都差强人意,所以deque是无法替代vector和list的

  • 与vector相比,deque的优势:头部插入和删除时不需要搬移元素,效率特别高,而且在扩容时,直接开新的buffer,顶多是中控数组需要扩容,也不需要搬移大量的元素因此其效率是必vector高的。
  • 与list相比,deque的优势:其底层是连续空间,空间利用率比较高,不需要存储额外字段(prev和next)

但deque有一个致命的缺点:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,还不如直接拷贝到vector中再遍历,但是对stack和queue来说恰好是不需要遍历,所以STL将deque用其作为stack和queue的底层数据结构

选择deque的原因

stack是LIFO的结构,因此只需要有push_back和pop_back操作都可以作为stack的结构,如vector和list
【C++】STL中stack和queue的模拟实现&&deque讲解_第6张图片queue是FIFO的结构,因此只需要有push_back和pop_front操作都可以作为queue的结构,如list
【C++】STL中stack和queue的模拟实现&&deque讲解_第7张图片
但为何选择deque呢?

  • stack和queue不需要遍历(没有迭代器),刚好不需要用到deque的缺点,只需要固定的一端(stack)或两端(queue)操作
  • 在stack中扩容时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中扩容时,deque不仅效率高,而且cache命中率高,内存使用率高,不会存在很多内存碎片

所以deque虽然没能代替vector和list,但是适合stack和queue使用

既然是容器适配器,那么实现起来就非常简单了,只需要调用deque的接口就可以了,和调用vector和list的接口类似

stack和queue的模拟实现

插入1 2 3 4后stack和queue的pop顺序
【C++】STL中stack和queue的模拟实现&&deque讲解_第8张图片

Stack.h

#pragma once
//#include 
#include 
using namespace std;

namespace king
{	
	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() const
		{
			return _con.back();
		}
		T& top() 
		{
			return _con.back();
		}
		int size() const
		{
			return _con.size();
		}
		bool empty() const
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
	void stack_test1()
	{
		//stack的第二的参数默认是deque,但我们也可以用vector
		//不过还是用deque好
		//stack> st;
		stack<int> st;
		st.push(1);
		st.push(2);
		st.push(3);
		st.push(4);
		cout << "stack->  ";
		while (!st.empty())
		{	
			cout << st.top() << " ";
			st.pop();
		}
		cout << endl;
	}
}

Queue.h

#pragma once
#include 
using namespace std;

namespace king
{	
	template<class T, class Container=deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_front();
		}
		int size() const
		{
			return _con.size();
		}
		bool empty() const
		{
			return _con.empty();
		}
		const T& front() const
		{
			return _con.front();
		}
		const T& back() const
		{
			return _con.back();
		}
	    T& front() 
		{
			return _con.front();
		}
		T& back() 
		{
			return _con.back();
		}
	private:
		Container _con;
	};
	void queue_test1()
	{
		queue<int> q;
		q.push(1);
		q.push(2);
		q.push(3);
		q.push(4);
		cout << "queue->  ";
		while (!q.empty())
		{	
			cout << q.front() << " ";
			q.pop();
		}
		cout << endl;
	}
}

Test.cpp

#include 
#include "Stack.h"
#include "Queue.h"

int main()
{	
	king::stack_test1();
	king::queue_test1();

	return 0;
}

你可能感兴趣的:(c/c++,c语言,开发语言,c++,数据结构)