目录
C++:stack queue的介绍及模拟实现
stack:
1.stack是一种容器适配器,专门用在具有后进先出操作的环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2.stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,元素特定容器的尾部(即栈顶)被压入和弹出。
3.stack的底层容器可以是任何标准的容器类模板或者是一些其他特定的容器类,这些容器应该支持以下操作:
empty | 判空 |
back | 获取尾部元素 |
push_back | 尾部插入元素 |
pop_back | 尾部删除元素 |
4.标准容器vector、deque、list均符号要求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。
stack的定义方式
方式一:使用默认的适配器定义栈
stack<int> st1;
方式二:使用特定的适配器定义栈
stack<int, vector<int>> st2;
stack<int, list<int>> st3;
stack的使用
stack当中常用的成员函数如下:
成员函数 | 功能 |
empty | 判断栈是否为空 |
size | 获取栈中有效元素个数 |
top | 获取栈顶元素 |
push | 元素入栈 |
pop | 元素出栈 |
swap | 交换两个栈中的数据 |
示例:
#include<iostream>
using namespace std;
#include<stack>
#include<vector>
int main()
{
stack<int, vector<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
cout << st.size() << endl;
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
cout << endl;
return 0;
}
queue:
1.队列是一种容器适配器,专门用于在FIFO环境操作,其中从容器一端插入元素,另一端提取元素
2.队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定成员函数来访问其元素,元素从队尾入队列,从队头出数列。
3.底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类,该底层容器应至少支持以下操作:
empty | 检测队列是否为空 |
size | 返回队列中有效元素的个数 |
front | 返回队头元素的引用 |
back | 返回队尾元素的引用 |
push_back | 在队列尾部入队列 |
pop_front | 在队列头部出数列 |
4.标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。
queue的定义方式
方式一:使用默认的适配器定义队列
queue<int> q1;
方式二:使用特定的适配器定义队列
queue<int, list<int>> q2;
queue的使用
成员函数 | 功能 |
empty | 判断队列是否为空 |
size | 获取队列中有效元素个数 |
front | 获取队头元素 |
back | 获取队尾元素 |
push | 队尾入数列 |
pop | 队头出数列 |
swap | 交换两个队列中的数据 |
示例:
#include<iostream>
using namespace std;
#include<stack>
#include<list>
#include<queue>
int main()
{
queue<int, list<int>> q;
q.push(1);
q.push(2);
q.push(3);
q.push(4);
cout << q.size() << endl;
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
cout << endl;
return 0;
}
stack queueOJ训练:
最小栈
class MinStack
{
public:
MinStack()
{
//调用默认构造函数即可
}
void push(int val)
{
_st.push(val);
if (_minst.empty() || val <= _minst.top())
{
_minst.push(val);
}
}
void pop()
{
if (_minst.top() == _st.top())
{
_minst.pop();
}
_st.pop();
}
int top()
{
return _st.top();
}
int getMin()
{
return _minst.top();
}
private:
stack<int> _st;
stack<int> _minst;
};
栈的弹出压入序列
class Solution
{
public:
bool IsPopOrder(vector<int>& pushV, vector<int>& popV)
{
stack<int> st;
size_t popi = 0;
for (auto e : pushV)
{
st.push(e);
//跟出栈序列比较 能匹配就持续输出
while (!st.empty() && st.top() == popV[popi])
{
st.pop();
++popi;
}
}
return st.empty();
}
};
逆波兰表达式求值
class Solution
{
public:
int evalRPN(vector<string>& tokens)
{
stack<int> st;
for(auto str : tokens)
{
if(str == "+" || str == "-" || str == "*" || str == "/")
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
switch(str[0])
{
case '+':
st.push(left + right);
break;
case '-':
st.push(left - right);
break;
case '*':
st.push(left * right);
break;
case '/':
st.push(left / right);
break;
}
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
用栈实现队列
class MyQueue
{
public:
MyQueue()
{
//使用默认构造函数即可
}
void push(int x)
{
_pushST.push(x); //入数据直接对pushST进行压栈
}
int pop()
{
int front = peek(); //获取popST栈顶元素,即“队头”元素
_popST.pop(); //将popST栈顶元素删除,即删除“队头”元素
return front; //返回删除的元素
}
int peek()
{
if (_popST.empty()) //如果popST为空,则先将pushST当中的全部元素压入popST
{
while (!_pushST.empty())
{
_popST.push(_pushST.top());
_pushST.pop();
}
}
return _popST.top(); //返回popST栈顶元素,即“队头”元素
}
bool empty() {
return _pushST.empty() && _popST.empty(); //pushST和popST同时为空,则“队列”为空
}
private:
stack<int> _pushST;
stack<int> _popST;
};
用队列实现栈
class MyStack
{
public:
MyStack()
{
//使用默认构造函数即可
}
void push(int x) {
//往非空的队列入数据
if (!_q1.empty())
{
_q1.push(x);
}
else
{
_q2.push(x);
}
}
int pop()
{
queue<int>* emptyQ = &_q1;
queue<int>* nonemptyQ = &_q2;
if (!_q1.empty())
{
swap(emptyQ, nonemptyQ);
}
//将非空队列的前n-1个元素导入空队列
while (nonemptyQ->size() > 1)
{
emptyQ->push(nonemptyQ->front());
nonemptyQ->pop();
}
int top = nonemptyQ->front();
nonemptyQ->pop();
return top;
}
int top()
{
if (!_q1.empty())
{
return _q1.back();
}
else
{
return _q2.back();
}
}
bool empty()
{
return _q1.empty() && _q2.empty();
}
private:
queue<int> _q1;
queue<int> _q2;
};
stack的模拟实现
namespace wjq
{
template <class T, class Container = std::deque<T>>
class stack
{
public:
bool empty()const
{
return _con.empty();
}
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
size_t size()const
{
return _con.size();
}
const T& top()const
{
return _con.back();
}
private:
Container _con;
};
}
queue的模拟实现
namespace wjq
{
template <class T, class Container = std::deque<T>>
class queue
{
public:
bool empty()const
{
return _con.empty();
}
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
size_t size()const
{
return _con.size();
}
const T& front()const
{
return _con.front();
}
const T& back()const
{
return _con.back();
}
private:
Container _con;
};
}
容器适配器
什么是适配器?
适配器是一种设计模式,设计模式是一套被反复使用的,多数人知晓的,经过分类编目的,代码设计经验的总结,该种模式是将一个类的接口转换成客户希望的另外一个接口
deque的原理介绍:
deque(双端队列):是一种双开口的连续空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素,与list比较,高速缓存利用率高,
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成,实际deque类似于一个动态的二维数组,其底层结构如下图所示:
双端队列底层是一段假想的连续空间,实际上却是分段连续的,为了维护其"整体连续"以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图:
deque的缺陷:
与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在中控数组扩容时,也不要搬移大量元素,因此其效率是比vector高的。
与list比较,其底层是连续空间,高速缓存利用率比较高,不需要存储额外字段
但是deque也有很大的缺陷:
1.不适合遍历和排序
因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下。
void TestOP()
{
srand(time(0));
const int N = 1000000;
vector<int> v;
v.reserve(N);
deque<int> dq;
for (int i = 0; i < N; ++i)
{
int e = rand();
v.push_back(e);
dq.push_back(e);
}
int begin1 = clock();
sort(v.begin(), v.end());
int end1 = clock();
int begin2 = clock();
sort(dq.begin(), dq.end());
int end2 = clock();
printf("vector sort:%d\n", end1 - begin1);
printf("deque sort:%d\n", end2 - begin2);
}
2.中间插入,删除效率不如list
从deque的底层结构图中可以看出,中间插入、删除数据仍会产生数据的挪动。deque中间插入、删除数据的速度不如list。
3.随机访问速度不如vector
由于deque的中控数组中指向的一段段地址空间之间并不连续,所以随机访问时需要计算目标数据处于哪段buffer中的第几个数据,所以deque的随机访问速度并没有vector快。
为什么选择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的效率高,扩容时不需要搬移大量数据,结合了deque的优点而又完美的避开了它的缺陷。