前言
stack
和 queue
使用起来都非常简单,现在来模拟实现一下,理解其底层的原理。
在实现之前,应该知道,stack
和 queue
都是容器适配器,通过看官网文件也可以看出来;其默认的容器都是deque
(双端队列)。
stack
和 queue
都是在容器的基础上进行了封装,实现了各自的操作。
(在下面的实现中stack
默认容器用vector
,queue
默认容器用list
)
栈和队列的使用
栈的使用
首先,STL中的栈是基于容器适配器实现的,默认容器是deque。
使用栈需要包含头文件:
#include<stack>
1.1 栈的基本操作
先看一下,栈提供的接口函数
常用方法:
push()
: 将元素val
压入栈顶。pop()
: 移除栈顶元素。top()
: 返回栈顶元素(但不移除)。empty()
: 判断栈是否为空。size()
: 返回栈的元素数量。
1.2 栈的使用
现在简单使用一下stack:
void test_stack() {
//创建一个栈——存储整形
stack<int> s;
//入栈
s.push(1);
s.push(2);
s.push(3);
//栈中元素
cout << "元素个数: " << s.size() << endl;
//栈顶元素
cout << "栈顶元素: " << s.top() << endl;
//出栈
s.pop();
//依次输出栈中的数据
while (!s.empty()) {
cout << s.top() << " ";
s.pop();
}
cout << endl;
return 0;
}
输出结果:
元素个数: 3
栈顶元素: 3
2 1
1.3 应用实例:点击消除
栈可以用来解决括号匹配这一类的问题。
括号匹配之前已经做过了,这里来用栈做这样的一道题:点击消除_牛客题霸_牛客网
题目描述:
这题思路比较简单,就是遍历字符串时,不断入栈,出栈(字符串中字符与栈顶元素相等);最后输出即可。
需要注意的是:这里使用数组来模拟栈,方便输出
当然也可以使用栈,不过输出时顺序是反的,需要进行处理。
#include <iostream>
using namespace std;
int main() {
string str;
cin>>str;
string ret;
for(auto ch: str)
{
if(ret.size()==0||ret[ret.size()-1]!=ch)
{
ret.push_back(ch);
}
else
{
ret.pop_back();
}
}
if(ret.empty())
{
cout<<'0';
return 0;
}
cout<<ret;
return 0;
}
队列的使用
2.1 队列的基本操作
STL
中的队列(queue
)也是一种容器适配器,默认底层容器也是 deque
。
使用栈需要包含头文件:
#include<queue>
常用方法:
push()
: 将元素val
插入队尾。pop()
: 移除队头元素。front()
: 返回队头元素(不移除)。back()
: 返回队尾元素(不移除)。empty()
: 判断队列是否为空。size()
: 返回队列的元素数量。
2.2 队列的使用
简单使用一下队列:
void test_queue()
{
//创建一个队列——存储整型
queue<int> q;
//入队列
q.push(10);
q.push(20);
q.push(30);
cout << "数据个数: " << q.size() << endl;
cout << "队头元素: " << q.front() << endl;
cout << "队尾元素: " << q.back() << endl;
//出队列
q.pop();
cout << "队列中数据: ";
while (!q.empty()) {
cout << q.front() << " ";
q.pop();
}
cout << endl;
}
输出结果:
数据个数: 3
队头元素: 10
队尾元素: 30
队列中数据: 20 30
栈和队列的模拟实现
stack
模拟
学习过初阶数据结构的栈,大概知道栈是怎么实现的,使用过STL里的stack
也知道具体哪些操作,这里就直接开始实现。
1.默认成员函数
对于默认成员函数,因为这里stack
是对容器的封装,所以那些构造函数、析构函数、赋值运算符重载等都不需要我们自己去实现,编译器默认生成的会去调用vector(容器)的默认成员函数。
template<class T, class Container = vector<T>>
class stack
{
stack() {}
private:
Container _con;
};
2.基本操作
栈的基本操作无疑就是,入栈、出栈、取栈顶元素、判断栈是否为空。
入栈:
直接调用容器vector
的尾插即可。
void push(const T& x)
{
_con.push_back(x);
}
出栈:
直接调用容器vector
的尾删
void pop()
{
_con.pop_back();
}
取栈顶元素:
直接返回容器vector
的最后一个元素即可,即调用back()
函数
T& top()
{
return _con.back();
}
const T& top()const
{
return _con.back();
}
判断是否为空:
调用容器的empty
函数即可
bool empty() const
{
return _con.empty();
}
返回栈中元素个数:
size_t size() const
{
return _con.size();
}
swap
直接调用容器的swap
函数即可。
void swap(stack& st)
{
_con.swap(st._con);
}
到这里 就简单实现了我们的stack
结构了,是不是感觉很简单呢?
template<class T, class Container = vector<T>>
class stack
{
public:
stack() {}
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();
}
const T& top()const
{
return _con.back();
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
void swap(Container& st)
{
_con.swap(st._con);
}
private:
Container _con;
};
queue
模拟
默认成员函数
和stack
一样,我们不需要去实现它的默认成员函数,编辑器默认生成的就已经可以满足我们的需求了。
基本操作
push
入队列,在队尾插入
void push(const T& x)
{
_con.push_back(x);
}
pop
出队列,从头部删除
void pop()
{
_con.pop_front();
}
front
和back
返回队头数据 / 队尾数据
T& back()
{
return _con.back();
}
const T& back() const
{
return _con.back();
}
T& front()
{
return _con.front();
}
const T& front()const
{
return _con.front();
}
empty
bool empty() const
{
return _con.empty();
}
size
size_t size() const
{
return _con.size();
}
swap
void swap(Container& con)
{
_con.swap(con);
}
到这里stack
和queue
的模拟实现就完成了,感觉是不是很容易呢。
三、双端队列(deque
)的栈和队列操作
deque
(双端队列)既可以当作栈,也可以当作队列使用。我们可以在双端队列的两端进行插入和删除操作,从而更灵活地实现栈和队列的功能。
对于双端队列,简单来说,就是可以像vector那样随机访问,也可以像链表那样随机位置插入;但是,效率很低,(个人感觉,就是为了给stack
和queue
作默认容器适配器来用的。
这里就不过多描述双端队列了,感兴趣可以去看一下源码学习学习。
继续加油!!!
_con.size();
}
**`swap`**
~~~cpp
void swap(Container& con)
{
_con.swap(con);
}
到这里stack
和queue
的模拟实现就完成了,感觉是不是很容易呢。
三、双端队列(deque
)的栈和队列操作
deque
(双端队列)既可以当作栈,也可以当作队列使用。我们可以在双端队列的两端进行插入和删除操作,从而更灵活地实现栈和队列的功能。
对于双端队列,简单来说,就是可以像vector那样随机访问,也可以像链表那样随机位置插入;但是,效率很低,(个人感觉,就是为了给stack
和queue
作默认容器适配器来用的。
这里就不过多描述双端队列了,感兴趣可以去看一下源码学习学习。
继续加油!!!