文章目录
-
- 一、stack/queue底层实现
-
- 二、stack/queue实战应用
- 1.[最小栈](https://leetcode.cn/problems/min-stack/)
- 2.[栈的弹出压入序列](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&&tqId=11174&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking)
- 3.[逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/submissions/)
文章目录
- 一、stack/queue底层实现
- 二、stack/queue实战应用
- 1.[最小栈](https://leetcode.cn/problems/min-stack/)
- 2.[栈的弹出压入序列](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&&tqId=11174&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking)
- 3.[逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/submissions/)
一、stack/queue底层实现
1.stack和queue模拟实现
stl中stack和queue的实现与其他数据结构不同,stack和queue都用是用的一个dequeue双端队列作为底层适配容器的基础上实现的.
stack.h
#pragma once
#include<iostream>
#include<vector>
using namespace std;
namespace kk
{
template <class T, class container = vector<T>>
class stack
{
public:
void push(const T& val)
{
_con.push_back(val);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
container _con;
};
void test_stack()
{
kk::stack<int> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
cout << endl;
}
}
queue.h
#pragma once
#include<iostream>
#include<list>
using namespace std;
namespace xj
{
template <class T, class container = list<T>>
class queue
{
public:
void push(const T& val)
{
_con.push_back(val);
}
void pop()
{
_con.pop_front();
}
T& front()
{
return _con.front();
}
T& back()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
container _con;
};
void test_queue()
{
xj::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;
}
}
2.dequeue实现思想
dequeue是一个柔和了list和vector的一些优点的新容器;同时支持list和vector的所有功能;dequeue的底层实现又是用vector作为适配器,有一个主存数组用来存储指针,指针指向固定大小的数组,地址从主存的中间位置开始是存储;
头插是从左边新开辟数组的尾部开始插入,尾插是从当前数组的尾部插入,头插头删,尾插尾删都不需要挪动数据,效率很高.
访问可用元素位置减去左边第一个数组的大小,然后除以固定数组的大小确定数组位置,然后在%固定数组的大小确定元素位置,所以也支持随机访问.
中间位置插入,因为固定大小的数组不能扩容,所以只能挪动数据;但是如果将固定大小的数组改为可变大小的数组,那么就不支持随机访问了.所以不能兼顾访问和中间插入的效率.
迭代器的位置需要用四个成员记录,固定数组的头,固定数组的尾,当前元素位置,当前数组位置;在一个数组中访问元素,只需要边用cur就可以,跨数组访问需要将数组位置更新至下一个数组,然后更新数组的头和尾.让cur指向数组第一个元素即可.
3.dequeue与vector和list优缺点总结
list优点:任意位置插入删除效率高,不需要扩容;缺点:不支持随机访问,.
vector优点:支持随机访问,尾插尾删效率高;缺点:头插和中间插入删除效率低.扩容代价高.
dequeue优点:支持随机访问但是没有vector效率高,尾插尾删效率高,扩容只需要拷贝指针即可,代价很低;缺点:中间插入删除效率低或者是不支持随机访问.
二、stack/queue实战应用
1.最小栈
用两个栈实现,一个栈正常出入栈,一个栈用来记录当前栈中的最最小值,如果最小栈是空则入栈,或者是val小于等于栈顶入栈,出栈时正常栈栈顶等于最小栈的栈顶则出栈,否则不出.最小栈的栈顶就是最小值.
class MinStack {
public:
void push(int val) {
_st.push(val);
if(_minst.empty()||val<=_minst.top())
{
_minst.push(val);
}
}
void pop() {
if(_st.top()==_minst.top())
{
_minst.pop();
}
_st.pop();
}
int top() {
return _st.top();
}
int getMin() {
return _minst.top();
}
private:
stack<int> _st;
stack<int> _minst;
};
2.栈的弹出压入序列
如果栈顶==popi就出栈并更新popi位置,直到栈为空或者栈顶不等于popi,否则就入栈pushi并更新pushi的位置.
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
int pushi=0,popi=0;
stack<int> st;
while(pushi!=pushV.size())
{
st.push(pushV[pushi++]);
while(!st.empty()&&st.top()==popV[popi])
{
st.pop();
popi++;
}
}
return st.empty();
}
};
3.逆波兰表达式求值
用一个栈辅助,如果tokens中是数字就入栈,如果不是就将栈顶的两个元素按照右左的顺序出栈并记录,然后判断是什么运算符,根据运算符确定left和right进行什么运算,将运算结果存储在栈中.最后栈顶就是结果
扩展:中缀变后缀的实现原理:用一个栈和一个string辅助实现,如果字符串中是数字就放入string,如果是运算符,同时栈是空或者栈顶的运算符优先级高于或者等于字符串中的运算符就出栈运算符放入string中,否则就入栈字符串中的运算符.处理完一个更新字符串字符索引位置到下一个位置.
比较特殊的情况是有括号的时候,当遇到左括号就无脑入栈,括号内的运算符按照其那面的逻辑出入栈,遇到有括号就认为右括号优先级是最低的,将括号内的运算符全部出栈,如果有括号遇到左括号就抵消处理.
class Solution {
public:
int evalRPN(vector<string>& tokens) {
int left=0,right=0;
stack<int> st;
for(auto str:tokens)
{
//这里如果直接使用单个字符比较会将复数算进去,所以使用string的比较重载函数
if(str=="+"||str=="-"||str=="*"||str=="/")
{
right=st.top();
st.pop();
left=st.top();
st.pop();
//Switch只支持整型比较,char也属于整型
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;
}
default :
break;
}
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};