一.stack
(一).stack简介
1. stack
是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2. stack
是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(
即栈顶
)
被压入和弹出。
3. stack
的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
· empty
:判空操作
· back
:获取尾部元素操作
· push_back
:尾部插入元素操作
· pop_back
:尾部删除元素操作
4.
标准容器
vector
、
deque
、
list
均符合这些需求,默认情况下,如果没有为
stack
指定特定的底层容器,默认情况下使用deque
。
(二).标准库里的stack类
可以看到,stack是LIFO,就是后进先出。
int main()
{
stack<int> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
//stack&queue是没有迭代器的,因为这两个有先进先出后进先出规则
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
cout << endl;
return 0;
}
(三).stack模拟实现(vector实现)
#include<vector>
namespace bite
{
template<class T>
class stack
{
public:
stack() {}
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_back(); }
T& top() { return _c.back(); }
const T& top()const { return _c.back(); }
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
private:
std::vector<T> _c;
};
}
(四).stack-oj题
二.queue
(一).queue简介
1.
队列是一种容器适配器,专门用于在
FIFO上下文
(
先进先出
)
中操作,其中从容器一端插入元素,另一端提取元素。
2.
队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,
queue
提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
3.
底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty
:检测队列是否为空
size
:返回队列中有效元素的个数
front
:返回队头元素的引用
back
:返回队尾元素的引用
push_back
:在队列尾部入队列
pop_front
:在队列头部出队列
4.
标准容器类
deque
和
list
满足了这些要求。默认情况下,如果没有为
queue
实例化指定容器类,则使用标准容器deque
。
(二).标准库里的queue类
int main()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
q.push(4);
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
cout << endl;
return 0;
}
#include <list>
namespace bite
{
template<class T>
class queue
{
public:
queue() {}
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_front(); }
T& back() { return _c.back(); }
const T& back()const { return _c.back(); }
T& front() { return _c.front(); }
const T& front()const { return _c.front(); }
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
private:
std::list<T> _c;
};
}
三.容器适配器
1.适配器
适配器是一种设计模式
(
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)
,
该种模式是将一个类的接口转换成客户希望的另外一个接口
。
2.stack&queue底层原理
可以看到,stack和queue都是由deque确定来的,不过stack和queue是截然不同的,一个是LIFO,一个是FIFO,那么是怎么实现的呢?我们来了解一下deque。
3.deque简介
deque(
双端队列
)
:是一种双开口的
"
连续
"
空间的数据结构
,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1)
,与
vector
比较,头插效率高,不需要搬移元素;与
list
比较,空间利用率比较高。
deque
并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际
deque
类似于一个动态的二维
数组
,其底层结构如下图所示:
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其
“
整体连续
”
以及随机访问的假象,落
在了
deque
的迭代器身上,
因此
deque
的迭代器设计就比较复杂,如下图所示:
如下图,头插尾插直接插入即可。
2.标准库里的deque类
int main()
{
deque<int> dq;
dq.push_back(1);
dq.push_back(2);
dq.pop_back();
dq.push_back(4);
dq.pop_front();
dq.push_front(6);
for (size_t i = 0; i < dq.size(); i++)
{
cout << dq[i] << " ";
}
cout << endl;
return 0;
}
四.deque,vector,list之间的比较
1.vector&list的优缺点
vector:
list:
2.deque相较于vector&list的优缺点
(1).优点
与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素(直接扩中控指针数组),因此其效率是必vector高的。
与list比较,底层是连续空间,空间利用率比较高,不需要存额外字段。并且支持下标随机访问。
(2).缺点
deque
有一个致命缺陷:不适合遍历,因为在遍历时,
deque
的迭代器要频繁的去检测其是否移动到
某段小空间的边界,导致效率低下
,而序列式场景中,可能需要经常遍历,因此
在实际中,需要线性结构
时,大多数情况下优先考虑
vector
和
list
,
deque
的应用并不多,而
目前能看到的一个应用就是,
STL
用其作
为
stack
和
queue
的底层数据结构。
头插尾插特别合适,但是中间插入就是勾史里的勾史,下面具体分析一下:
void test_op()
{
srand(time(0));
const int N = 1000000;
vector<int> v1;
vector<int> v2;
v1.reserve(N);
v2.reserve(N);
//deque头插头删尾插尾删特别厉害,配合stack&queue特别合适
deque<int> dq1;
deque<int> dq2;
for (int i = 0; i < N; ++i)
{
auto e = rand();
//v1.push_back(e);
//v2.push_back(e);
dq1.push_back(e);
dq2.push_back(e);
}
// 拷贝到vector排序,排完以后再拷贝回来
int begin1 = clock();
// 先拷贝到vector
for (auto e : dq1)
{
v1.push_back(e);
}
// 排序
sort(v1.begin(), v1.end());
// 拷贝回去
size_t i = 0;
for (auto& e : dq1)
{
e = v1[i++];
}
int end1 = clock();
int begin2 = clock();
//sort(v2.begin(), v2.end());
sort(dq2.begin(), dq2.end());
int end2 = clock();
// deque直接排序都不如拷贝到vector排序快,说明deque性能是真的一般
printf("deque copy vector sort:%d\n", end1 - begin1);
printf("deque sort:%d\n", end2 - begin2);
}
int main()
{
test_op();
}
第一个是deque拷贝到vector后排序,第二个是deque直接排序,拷贝到vector都比直接deque sort快,说明deque性能是勾史里的勾史。
五.deque实现stack&queue
1.deque实现stack
#include<deque>
namespace bite
{
template<class T, class Con = deque<T>>
//template<class T, class Con = vector<T>>
//template<class T, class Con = list<T>>
class stack
{
public:
stack() {}
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_back(); }
T& top() { return _c.back(); }
const T& top()const { return _c.back(); }
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
private:
Con _c;
};
}
2.deque实现queue
#include<deque>
namespace bite
{
template<class T, class Con = deque<T>>
//template<class T, class Con = list<T>>
class queue
{
public:
queue() {}
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_front(); }
T& back() { return _c.back(); }
const T& back()const { return _c.back(); }
T& front() { return _c.front(); }
const T& front()const { return _c.front(); }
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
private:
Con _c;
};
}
3.为什么使用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
的优点,而完美的避开了其缺陷。
六.priority_queue(优先级队列)
1.priority_queue简介
1.
优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2.
此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素
(
优先队列中位于顶部的元素)
。
3.
优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,
queue
提供一组特定的成员函数来访问其元素。元素从特定容器的“
尾部
”
弹出,其称为优先队列的顶部。
4.
底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
· empty()
:检测容器是否为空
· size()
:返回容器中有效元素个数
· front()
:返回容器中第一个元素的引用
· push_back()
:在容器尾部插入元素
· pop_back()
:删除容器尾部元素
5.
标准容器类
vector
和
deque
满足这些需求。默认情况下,如果没有为特定的
priority_queue
类实例化指定容器类,则使用vector
。
6.
需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、
push_heap
和
pop_heap
来自动完成此操作