实现方式与基本操作:
栈的特点:先进后出。
队列的特点:先进先出。
根据这两个特点实现的,提供接口的数据结构都是栈和队列。所以其实算是一个抽象概念,不是说就是库函数里面的stack与queue.
库函数中的栈与队列:
库函数提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。所以STL中栈与队列往往不被归类为容器,而被归类为container adapter(容器适配器)。
底层实现:我们常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的底层结构。deque是一个双向队列,只要封住一段,只开通另一端就可以实现栈的逻辑了。我们也可以指定vector为栈的底层实现或者list为队列的底层实现,初始化语句如下:
std::stack<int, std::vector<int> > third; // 使用vector为底层容器的栈
std::queue<int, std::list<int>> third; // 定义以list为底层容器的队列
常用接口:
栈 stack:
操作 | 含义 |
stk.push(ele) | 元素ele入栈,增加元素 O(1) |
s.pop() | 移除栈顶元素 O(1) |
s.top() | 取栈顶元素,但不删除 O(1) |
s.empty() | 判断当前栈是否为空,若为空返回1 O(1) |
s.size() | 返回栈内元素的个数 O(1) |
队列 queue :
操作 | 含义 |
que.front() | 返回队首元素大小 O(1) |
que.back() | 返回队尾元素大小 O(1) |
que.push() | 队尾添加元素入队O(1) |
que.pop() | 队首元素出队 O(1) |
que.size() | 返回队列大小 O(1) |
que.empty() | 判断当前队列是否为空,若为空返回1 O(1) |
双端队列 deque:
支持在两端插入与删除
操作 | 含义 |
push_back(x) / push_front(x) | 把x插入队尾后/队首 O(1) |
back() / front() | 返回队尾 / 队首元素 O(1) |
pop_back() / pop_front() | 删除队尾 / 队首元素 O(1) |
erase(iterator it) | 删除迭代器指向的某个元素 |
erase(iterator first,iterator last) | 删除[first,last)中的元素 |
empty() | 判断是否为空 O(1) |
size() | 返回deque的元素数量 O(1) |
clear() | 清空deque |
begin() | 起始迭代器 |
end() | 末尾迭代器 |
注意点:deque是可以排序的
特殊的队列:priority_queue:
本质上是一个大顶/小顶堆。
操作 | 含义 |
top() | 返回当前堆中的最大/小值 |
push() | 向堆中加入元素 |
pop() | 堆顶(队首)元素出队 |
size() | 返回堆的大小 |
empty() | 判断是否为空 |
注意点:
① 没有clear()
② 优先队列只能通过top()
访问队首元素(优先级最高的元素)
模板 :
template <class T, class Container = vector<T>,
class Compare = less<typename Container::value_type> >
class priority_queue;
参数解释 :
class T:T是优先队列中存储的元素的类型。
class Container = vector<T>:Container是优先队列底层使用的存储结构,可以看出来,默认采用vector。
class Compare = less<typename Container::value_type> :Compar是定义优先队列中元素的比较方式的类。
关于第三个参数:
① 常见的参数:
less<int> 这样创建的是大根堆,greater<int> 这样创建的是小根堆
priority_queue<int, vector<int>> q1; // 默认大根堆, 即每次取出的元素是队列中的最大值
priority_queue<int, vector<int>, less<int>> q2; // 大根堆
priority_queue<int, vector<int>, greater<int>> q3; // 小根堆, 每次取出的元素是队列中的最小值
这里要注意:sort的排序中 less是 升序,小的在前面。 greater是降序,大的在前面。这里的less是大数优先级高,大数先输出,greatrer是小数优先级高,小数在前面。
只需要记住参数含义跟sort是相反即可。
② priority_queue 中特殊的排序:
pair,二元组
排序规则:
默认先对pair
的first
进行降序排序,然后再对second
降序排序
对first
先排序,大的排在前面,如果first
元素相同,再对second
元素排序,保持大的在前面。
#include <iostream>
#include<algorithm>
#include <queue>
#define pii pair<int,int>
#define x first
#define y second
using namespace std;
int main() {
priority_queue<pii, vector<pii>> q;
q.push({ 7,8 });
q.push({ 7,9 });
q.push({ 9,8 });
while (q.size())
{
cout << q.top().x << " " << q.top().y << endl;
q.pop();
}
return 0;
}
③ 自定义排序写法:
一定要用一个结构体或者是类去重载符号。为什么呢?因为人家参数里面就是一个class,所以自己写一个函数是不行的。但是sort可以自己写函数。
如果现在我们相对pair中的排序按照second优先做降序:
#include <iostream>
#include<algorithm>
#include <queue>
#define pii pair<int,int>
#define x first
#define y second
using namespace std;
struct cmp1
{
bool operator()(const pii a, const pii b)
{
return a.y < b.y;
}
};
int main() {
priority_queue<pii, vector<pii>,cmp1> q;
q.push({ 7,8 });
q.push({ 7,9 });
q.push({ 9,10 });
while (q.size())
{
cout << q.top().x << " " << q.top().y << endl;
q.pop();
}
return 0;
}
数组实现栈与队列
栈:
定义 int stk[10000] , tt = 0,tt为指针,模拟栈的操作。
操作 | 含义 |
stk[++tt] = k | 栈中加入一个数k |
tt-- | 栈中出去一个数 |
stk[k] | 取栈顶元素 |
tt == 0 | 判断栈是否为空,返回1为空 |
tt | 表示栈内元素个数 |
int stk[10000], tt; //tt初始化为0,则空栈条件就是tt==0
int main()
{
//入栈
int k;
cin >> k;
stk[++tt] = k;
//出栈
//tt--;
//判断是否为空栈
!tt ? "空" : "不空";
for (int i = 0; i < 5; i++)
stk[++tt] = i;
while (tt)
{
cout << stk[tt] << endl;
tt--;
}
return 0;
}
队列:
定义 int q[100000], hh = 0, tt = -1 q[]表示一个队列,hh是队首指针,tt是队尾指针。
操作 | 含义 |
q[hh] | 队头元素大小 |
q[tt] | 队尾元素大小 |
q[++tt] = k | 往队列里加入一个k |
hh++ | 队尾元素出队 |
tt - hh +1 | 队中元素个数 |
hh<=tt | 判断是否队列为空 |
//数组模拟
//空队列 tt<hh
int q[1000], hh, tt = -1; // hh为头指针,tt 为尾指针
int main()
{
int x;
//入队列
/*q[++tt] = x;*/
//出队列
/*hh++;*/
for (int i = 0; i < 6; i++)
q[++tt] = i;
while (hh <= tt)
{
cout << q[hh] << endl;;
hh++;
}
return 0;
}
数组模拟栈与队列就是模拟头尾指针的移动。只要记住栈stk的栈顶指针tt从0开始开始,queue的队列头指针tt从-1开始,hh从0开始即可。(从其他值开始也可以,看个人习惯)
练习题:
力扣 232.用栈实现队列
两个栈就可以模拟队列的操作:一个进栈,一个出栈。
入队操作:直接把元素放入进栈就可以了。
出队操作:如果出栈中没有元素,则需要把进栈的元素全部压入出栈中,然后弹出出栈的一个元素。如果栈中还有元素,则直接弹出原来的元素就行了。
返回栈顶元素:把出栈的元素弹出再弹入即可。
code:
class MyQueue {
public:
stack<int> stin,stout;
MyQueue() {
}
void push(int x) {
stin.push(x);
}
int pop() {
if(!stout.size())
{
while(stin.size())
{
stout.push(stin.top());
stin.pop();
}
}
int res = stout.top();
stout.pop();
return res;
}
int peek() {
int res = this ->pop();
stout.push(res);
return res;
}
bool empty() {
return stin.empty()&& stout.empty();
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
力扣 225. 用队列实现栈
题目说两个队列实现栈,其实一个队列就可以实现栈。
入栈:把元素直接加入队列即可。
出栈:如果队列不空的话,将队首元素弹出再弹入,重复size-1次即可。其实就是把队尾元素从队首弹出。
code:
class MyStack {
public:
queue<int> que;
MyStack() {
}
void push(int x) {
que.push(x);
}
int pop() {
int n = que.size();
while(--n) // n--做n次运算,--n做n-1次运算
{
int res = que.front();
que.pop();
que.push(res);
}
int res = que.front();
que.pop();
return res;
}
int top() {
return que.back();
}
bool empty() {
return que.empty();
}
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/
力扣 20. 有效的括号
不合法的情况一共有三种:
1. 左括号多了 2.右括号多了 3.当前括号与前一个括号不匹配
那该如何匹配呢?用栈记录。如何匹配左右括号?用哈希表比较好写一点。
code:
class Solution {
public:
stack<char> st;
map<char,char> mp{{'[',']'},{'{','}'},{'(',')'}};
bool isValid(string s) {
for(auto i:s)
{
if( i == '('||i == '{'||i=='[')
st.push(i);
else
{
if(!st.size()) return 0;
if(mp[st.top()] == i)
st.pop();
else return 0;
}
}
return st.size() == 0;
}
};
力扣1047. 删除字符串中的所有相邻重复项
匹配的问题用要想到用栈去解决
code:
class Solution {
public:
stack<char> sk;
string removeDuplicates(string s) {
for(auto i:s)
{
if(!sk.size()) sk.push(i);
else
{
char ch = sk.top();
if(ch == i)
sk.pop();
else sk.push(i);
}
}
string ans;
while(sk.size())
{
ans += sk.top();
sk.pop();
}
reverse(ans.begin(),ans.end());
return ans;
}
};
力扣150. 逆波兰表达式求值
(a+b)*c-(a+b)/e的后缀表达式为 ab+c*ab+e/-
遇到数字就加入栈,遇到字符就把栈中前两个字符拿出来运算完再放进去,最后的答案就是结果了。
code: stoll表示string转换成longlong类型的整数。常见的是stoi
class Solution {
public:
stack<long long> stk;
int evalRPN(vector<string>& tokens) {
for(int i = 0;i<tokens.size();i++)
{
if(tokens[i] =="+"||tokens[i] =="-"||tokens[i] =="*"||tokens[i] =="/")
{
long long x,y;
x = stk.top();
stk.pop();
y = stk.top();
stk.pop();
if(tokens[i] == "+") stk.push(x+y);
if(tokens[i] == "-") stk.push(y-x);
if(tokens[i] == "*") stk.push(x*y);
if(tokens[i] == "/") stk.push(y/x);
}
else stk.push(stoll(tokens[i]));
}
return stk.top();
}
};
力扣239. 滑动窗口最大值
经典来了。用队列来实现滑动窗口,这个队列的特点是:
1. 是单调的队列:队列中的元素都是递增或者是递减的。
2. 队列不是单向的,而是双端队列(deque),需要再两端进栈或者是出栈。
为什么不用大顶堆去做?堆的首元素不就是最大值。因为这个窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值了。所以不能用大顶堆。
所以我们实现的是单调队列。
对于队头方向:默认前提是队中元素已经是单调的了。每次出队列的判断:当前队列的长度超过了k。
对于队尾方向:当前进队的数如果比最后的元素大,需要从队尾弹出所有比进队元素小的数。
收集答案:如果数列长度为n = 10,窗口的大小规定为k = 3,最终的答案会是8个,所有一共会用 n - k + 1个答案。
队列中记录的是数列的下标,为什么用下标?因为下标是从0开始严格单调递增的。我们可以利用下标去进行一些判断。
code:
用deque的去实现:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> q;
int n = nums.size();
vector<int> ans;
for(int i = 0;i<n;i++)
{
while(q.size() && i-q.front()+1 > k ) q.pop_front();
while(q.size() && nums[i] >= nums[q.back()]) q.pop_back();
q.push_back(i);
if(i >= k - 1) ans.push_back(nums[q.front()]);
}
return ans;
}
};
用数组去实现:
class Solution {
public:
vector<int> ans ;
int que[100005], hh = 0, tt = -1; //hh为队头,tt为队尾
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
for(int i = 0;i<nums.size();i++)
{
while(hh<=tt && i-que[hh]+1 > k) hh++; // 判断队头存的下标
while(hh<=tt && nums[que[tt]] <= nums[i]) tt--; //判断队尾的下标
que[++tt] = i; // 插入到队尾
if(i >= k - 1) ans.push_back(nums[que[hh]]); // 队头元素合法
}
return ans;
}
};
注意: 不管是栈还是队列,弹出的时候都要判断下是否已经空了。空的栈和队列是不能弹出的。
力扣 347.前 K 个高频元素
自然会想到用哈希表去记录每个元素出现的频次,但是有个问题,利用map我们可以很轻松的从key找到value,但是无法从value找到key。
其实这就是个排序的问题。如果我们存完数据按照value去排序就好了。
一种方法是利用小顶堆。如果堆中元素不等于k,我们就一直pop,最后留下的k个元素就是我们要的答案。
code: 注意 priority_queue或者是map的cmp要写一个类或者是结构体。
class Solution {
public:
class mycompare
{
public:
bool operator()(pair<int,int>& a,pair<int,int>& b)
{
return a.second > b.second ;
}
};
unordered_map<int,int> mp;
vector<int> topKFrequent(vector<int>& nums, int k) {
for(auto i:nums) mp[i]++;
priority_queue<pair<int,int>,vector<pair<int,int>>,mycompare> que;
for(auto it = mp.begin();it!=mp.end();it++)
{
que.push(*it);
if(que.size() > k)
que.pop();
}
vector<int> ans;
while(que.size())
{
int x = que.top().first;
que.pop();
ans.push_back(x);
}
return ans;
}
};
如果不想用priority的话,可以用vector的sort,按照second的降序去对pair排序即可。
code: sort 的 cmp是用函数实现的。
class Solution {
public:
unordered_map<int,int> mp;
vector<int> ans;
vector<pair<int,int>> t;
static bool cmp( pair<int,int> lhs, pair<int,int> rhs)
{
return lhs.second >= rhs.second;
}
vector<int> topKFrequent(vector<int>& nums, int k) {
for(int i :nums) mp[i]++;
for(auto it = mp.begin();it!=mp.end();it++)
t.push_back(*it);
sort(t.begin(),t.end(),cmp);
for(int i = 0,j = 0;i<t.size();i++)
{
ans.push_back(t[i].first);
j++;
if(j == k) break;
}
return ans;
}
};