前言
为什么把栈和队列放在一块讲呢?因为我认为这两种数据结构的代码实现和代码思想基本上可以说是一模一样的。这两中数据结构算是比较简单的,但用途却是很广泛的,非常重要。
栈
栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。 栈中的数据结构遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈,出数据也在栈顶。
其实这个东西看起来很高大上,其实很简单,你只需要把它想成往一个桶里装东西,往出拿的时候,先放进去的后出来,后放进去的先拿出来。
基础代码实现
进栈
void push(ll x)
{
a[++ tt] = x;//用数组进入一个元素
}
出栈
void pop()
{
tt --;//下标减一就相当于数组弹出顶上内个元素
}
检验栈内是否为空
string empty()
{
return tt == -1 ? "YES" : "NO";//由于tt刚开始初始化为-1,每加入一个元素就++tt,每弹出一个元素就tt--,则tt == -1时证明栈内为空,否则不为空。
}
查询栈顶的元素
ll query()
{
return a[tt];//返回栈顶下标的值即可
}
模拟栈
代码实现
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll a[N] = {0};
int tt = -1;
void push(ll x)
{
a[++ tt] = x;
}
void pop()
{
tt --;
}
string empty()
{
return tt == -1 ? "YES" : "NO";
}
ll query()
{
return a[tt];
}
void solve()
{
string op;
ll x;
cin >> op;
if(op[2] == 's')
{
cin >> x;
push(x);
}
else if(op[1] == 'o')
{
pop();
}
else if(op[0] == 'e')
{
cout << empty() << endl;
}
else if (op[0] == 'q')
{
cout << query() << endl;
}
}
int main()
{
int m;
cin >> m;
while(m --)
{
solve();
}
return 0;
}
STL
其实如果我们使用的是c++语言的话,在做oj题时我们通常并不需要自己用数组来实现栈(除非对时间要求特别严格),在c++的STL中这么一个容器叫stack,它可以帮助我们实现栈的功能。
头文件:
我们在使用这个容器之前要加一个头文件(用了万能头的就不需要了)
#include<stack>
初始化:
stack<T> a;//其中T代表的是栈中要储存的数据类型,a代表的是变量名。
//如: stack<int> a;
操作:(a为变量名)
操作 | 功能 |
---|---|
a.push() | 向栈顶添加元素 |
a.pop() | 弹出栈顶的元素 |
a.size() | 返回栈中元素的个数 |
a.top() | 查询栈顶的元素 |
a.empty() | 判断栈中是否为空,为空则返回真 |
Note: stack中是没有 a.clear() 这个操作的。
遍历:
while(!a.empty())
{
cout << a.top() << " ";
a.pop();
}
当然这种遍历的话,遍历完了之后也意味着栈中的元素也没了。
Note:stack 是不可以用迭代器和for(auto i :a)来遍历的。
典型例题
这是来自Leetcode的一道的关于栈的典型例题
代码实现:
class Solution {
public:
bool isValid(string s) {
stack<char> str;
for(int i = 0;i < s.size();i ++)
{
if(s[i] == "(" || s[i]=='{' || s[i]=='[') str.push(s[i]);
else if(s[i] == ')')
{
if(str.empty()) return false;
if(str.top() == '(') str.pop();
else return false;
}
else if(s[i] == '}')
{
if(str.empty()) return false;
if(str.top() == '{') str.pop();
else return false;
}
else if(s[i] == ']')
{
if(str.empty()) return false;
if(i == 0) return false;
if(str.top() == '[') str.pop();
else return false;
}
}
if(str.empty()) return true;
else return false;
}
};
队列
队列的概念及结构
队列:只允许在一端进行插入操作,在另一端进行删除操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的特点。
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
这个也是看起来高大上,其实你可以把它想成一个管子,先进来的先出去。
基础代码实现
入队列
void push(ll x)
{
a[++ tt] = x;//tt代表队尾
}
出队列
void pop()
{
hh ++;//hh代表队头
}
查询是否为空
string empty()
{
return hh <= tt ? "NO" : "YES";//当头>尾时代表队列尾为空(初始化tt = -1,hh = 1)
}
查询队头元素
int query()
{
return a[hh];
}
模拟队列
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll a[N], tt = -1,hh = 0;
void push(ll x)
{
a[++ tt] = x;
}
void pop()
{
hh ++;
}
string empty()
{
return hh <= tt ? "NO" : "YES";
}
int query()
{
return a[hh];
}
void solve()
{
string op;
ll x;
cin >> op;
if(op[2] == 's')
{
cin >> x;
push(x);
}
else if(op[1] == 'o')
{
pop();
}
else if(op[0] == 'e')
{
cout << empty() << endl;
}
else if (op[0] == 'q')
{
cout << query() << endl;
}
}
int main()
{
int m;
cin >> m;
while(m --)
{
solve();
}
return 0;
}
STL
和栈一样,C++也有一个队列的容器queue.
头文件
#include<queue>
初始化
queue<T> a;//T代表数据类型,a代表变量名
//如:queue<int> a;
操作
操作 | 功能 |
---|---|
a.push() | 在队尾插入一个元素 |
a.pop() | 在队头弹出一个元素 |
a.size() | 返回队列中元素的个数 |
a.empty() | 判断队列是否为空 |
a.front() | 查询队头元素 |
a.back() | 查询队尾元素 |
Node:queue中也没有a.clear()这个操作
遍历
while(!a.empty())
{
cout << a.front() << " ";
a.pop();
}
Node:和stack一样,queue也不能用迭代器和for(auto i : a) 来进行遍历
典型例题
这是一道来自洛谷的关于队列的典型题目。
代码实现:
#include<bits/stdc++.h>
using namespace std;
queue<int> a;
int b[1100] = { 0 };
int main()
{
int m, n;
cin >> m >> n;
int cnt = 0;
for(int i = 1;i <= n;i ++)
{
int x;
cin >> x;
if(b[x]) continue;
else
{
if(a.size() >= m)
{
int y = a.front();
b[y] --;
a.pop();
}
a.push(x);
b[x] ++;
cnt ++;
}
}
cout << cnt;
return 0;
}