往期回顾
链接 | 内容 |
---|---|
string模拟实现 | STL中string的模拟实现 |
vector模拟实现 | STL中vector的模实现 |
list模拟实现 | list的模拟实现下星期补上来 |
stack和queue相必大家并不陌生,那么STL是如何实现的呢?它们俩虽然可以存放元素,但被归列在Container adaptors容器适配器中,先来认识一下什么是适配器
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。
简单来说就是将别人的东西贴上自己的标签变成自己的东西(套娃)
既然stack和queue是适配器,那么它们俩的底层就是对其他容器的接口进行封装变为自己的接口。
那么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
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的出现实际上是为了
融合list和vector的优点
,从而代替它们,显然vector和list并没有被代替
- vector的优点:支持下标随机访问,尾插尾删效率高,CPU高速缓存命中率高
- vector的缺点:不适合头插头删,时间复杂度O(N),可能存在空间浪费,需要异地扩容时效率低
- list的优点:按需申请空间,不存在空间浪费,任意位置插入删除的时间复杂度O(1)
- list的缺点:不支持下标随机访问,频繁的申请空间
deque(双端队列)
:是一种双开口的"连续"
空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高,不需要存储额外字段。
但deque并不是真正的连续空间(空间连续了但没完全连续),而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:
当然为了能够支持随机访问,deque的iterator迭代器就设计的十分复杂了
deque的迭代器封装了四个指针,first和last用来指向buffer的头尾,cur用来遍历buffer,然后将node指向中控数组对应位置
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的底层数据结构
stack是LIFO的结构,因此只需要有push_back和pop_back操作都可以作为stack的结构,如vector和list
queue是FIFO的结构,因此只需要有push_back和pop_front操作都可以作为queue的结构,如list
但为何选择deque呢?
- stack和queue不需要遍历(没有迭代器),刚好不需要用到deque的缺点,只需要固定的一端(stack)或两端(queue)操作
- 在stack中扩容时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中扩容时,deque不仅效率高,而且cache命中率高,内存使用率高,不会存在很多内存碎片
所以deque虽然没能代替vector和list,但是适合stack和queue使用
既然是容器适配器,那么实现起来就非常简单了,只需要调用deque的接口就可以了,和调用vector和list的接口类似
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;
}