堆栈和队列统称线性表
- 简单的线性结构
- 数组和链表可以实现这两种数据结构
堆栈
- 后进先出(LIFO)
- 用栈实现DFS的非递归遍历
栈的基本操作如下
头文件为: #include<stack>
定义:stack<type> s type为数据类型(如int float char等)
基本操作
s.push(item)
s.pop() //删除栈顶元素,但是不返回
s.top()
s.size()
s.empty()
队列
- 先进先出(FIFO)
- 用队列实现BFS遍历
队列的基本操作如下
头文件 #include\<queue>
queue<type> q type为数据类型(如int float char等)
基本操作有
q.push(item)
q.pop() //删除队首元素,但是不返回
q.front()
q.back() //队列不仅可以访问队首,还可以访问队尾
q.size()
q.empty()
题一 元素出入栈的合法性判断。
给定一些元素的入栈顺序和出栈顺序,问是否可能?(假设所有元素都不相同)
【分析】根据栈的特性,栈顶进,栈顶出,则栈顶元素恰好等于要出栈的元素,则必须出栈,否则继续入栈。当栈压完的时候,也会被弹完,如果还有的话,则出入栈的顺序不合法
bool isPossible(vector<int> _in, vector<int> _out) {
//使用in表示进栈顺序,out表示出栈顺序
stack<int> s;
int i, j;
for (i = 0, j = 0; i < _in.size(); i++) {
s.push(_in[i]);
while ((!s.empty()) && (s.top() == _out[j])) {
s.pop();
j++; //当前要出栈的元素
}
}
if (s.empty()) {
return true;
}
return false;
}
题二 用两个队列实现堆栈
【分析】根据队列和堆栈的特点,两个队列来回倒,保证一个空队列,用于存放另一个队列除了队尾元素的其他元素。
typedef class _mystack {
public:
void push(int a);
int pop(); //pop是不返回值的
int top();
bool empty();
int size();
private:
queue<int> q1;
queue<int> q2;
int len;
}mystack;
void mystack::push(int a) {
if (!this->q1.empty()) {
this->q1.push(a);
}
else {
this->q2.push(a);
}
}
int mystack::pop() {
if (this->q1.empty() && !this->q2.empty()) {
for (int i = 0; i < q2.size() - 1; i++) {
this->q1.push(this->q2.front());
this->q2.pop();
}
this->q2.pop();
}
else if (this->q2.empty() && !this->q1.empty()) {
for (int i = 0; i < this->q1.size()-1; i++) {
this->q2.push(this->q1.front());
this->q1.pop();
}
this->q1.pop();
}
else {
printf("current stack is empty, can not pop!!\n");
return -1;
}
return 0;
}
int mystack::top() {
if (this->q1.empty() && !this->q2.empty()) {
return this->q2.back();
}
else if (this->q2.empty() && !this->q1.empty()) {
return this->q1.back();
}
else {
printf("current stack is empty, can not pop!!\n");
return -1;
}
}
bool mystack::empty() {
return (this->q1.empty() || this->q2.empty());
}
int mystack::size() {
return this->q1.empty() ? this->q2.size() : this->q1.size();
}
题三,用两个栈实现一个队列
【分析】使用两个栈s1和s2,s1专门负责入队列,s2专门负责出队列,假如s2为空,就将整个s1弹出s2中。实际代码较长,下面只给出push()和pop()两部分的代码。
stack<int> s1; // 负责入队列
stack<int> s2; // 负责出队列
void push(int a) {
s1.push(a);
}
void pop() {
if (s2.empty()) {
while (!s1.empty()) {
s2.push(s1.top());
s1.pop();
}
}
s2.pop();
}
题四 支持查找最小元素的堆栈
一个堆栈除了支持push, pop以外还要支持一个操作getMin得到当前堆栈里所有元素的最小值
【分析】1. 笨方法是再开辟一个栈,要getMin的时候,将元素一个一个倒到新栈中,并顺便求最小值,最后再倒回原堆栈
2. 好一点的办法,是开辟一个栈stackMin,用来维护到目前为止的最小值,插入一个新值,如果比当前最小值小或者等于,则压入stackMin中,如果比当前stackMin.top()大的话,则维持stackMin不动.
从堆栈中弹出元素,如果弹出元素比stackMin.top()大的话,则维持stackMin不动,如果等于stackMin.top()则弹出stackMin的栈顶元素。下面贴出代码,供参考。
typedef class _minstack {
public:
void push(int a);
void pop();
int getMin();
private:
stack<int> sc;
stack<int> scMin;
}minstack;
void minstack::push(int a) {
sc.push(a);
if (scMin.empty()) {
scMin.push(a);
}
else if(a <= scMin.top()){
scMin.push(a);
}
}
void minstack::pop() {
if (sc.empty()) {
printf("stack is empty\n");
return;
}
if (sc.top() = scMin.top()) {
scMin.pop();
sc.pop;
return;
}
sc.pop();
}
题五 最大直方图
给出一个直方图,求最大面积矩形
【分析】如上图,想求矩形面积,得知道当前高度,左边界,右边界。可以使用堆栈来确定左右边界
对于每一块板,当前栈顶板子如果高于要加入的板子,则当前板子的右边界确定,左边界就是上一块板子,如果当前栈顶板子低于要加入的板子,则要加入的板子的左边界确定。可知堆栈里的元素是递增的。值得注意的是 1)栈底元素是目前最小的元素,所以左边界是-1。2)堆栈最后剩下的元素是的递增的,我们需要再循环计算一次。现贴出实现上述思想的代码:
#include <stdio.h>
#include <stack>
#include <vector>
#include <algorithm>
using namespace std;
int getMaxRec(vector<int> &rec) {
int res = 0;
int len = rec.size();
int i;
stack<int> stc;
for (i = 0; i < len; i++) {
while (!stc.empty() && rec[i] < rec[stc.top()]) {
int h = rec[stc.top()];
//栈底元素是到目前位置最小的数,则左边界是0
res = max(res, (h * (i - (stc.size() == 1 ? 0 : stc.top()))));
stc.pop();
}
stc.push(i);
}
i = stc.top();
while (!stc.empty()) {
int h = rec[stc.top()];
res = max(res, (h * (1 + i - (stc.size() == 1 ? 0 : stc.top()))));
stc.pop();
}
return res;
}
void main() {
vector<int> rec = { 2,1,5,6,2,3 };
int m = getMaxRec(rec);
printf("max rec is %d", m);
system("pause");
return;
}
题六 滑动窗口最大值
给定一个数组a[0…n],还有一个值k,计算数组b[i] = max(a[i – k + 1… i]) 注意认为负数下标对应值是无穷小。
分析:可以采用滑动窗口的思想,如果同时存在一个旧的数x,和一个新的数y并且x ≤ y,则x永远不会是我们要的解。我们使用一个双端队列,来模拟窗口,旧的值从队列前端删除,要加入的数,如果比队尾元素大的话,则队尾无效的数要被删除。总的来看要维护队列的单调性(递减的)。
#include <stdio.h>
#include <deque>
#include <vector>
using namespace std;
int winMax(vector<int> a, int k) {
if (a.size() < k) {
printf("size is error\n");
return -1;
}
vector<int> b;
deque<int> dq; // 窗口
for (int i = 0; i < a.size(); i++) {
while (!dq.empty() && dq.front() <= i - k) dq.pop_front();
while (!dq.empty() && a[i] >= a[dq.back()]) dq.pop_back();
dq.push_back(i); //队列中存放的是元素序号
b.push_back(a[dq.front()]);
}
for (int i = 0; i < b.size(); i++) {
printf("%d\t", b[i]);
}
}
void main() {
vector<int> a = { 5,1,4,3,2,4 };
winMax(a, 3);
system("pause");
}
对于上述代码的理解
旧的数比较大,因为“过期”而“不得不”出队
存放a数组的“下标”而没存放具体值
这里总结下双端队列和向量的用法
#include <deque>
#include <vector>
deque<int> dq;
dq.push_front();
dq.push_back()
dq.pop_front();
dq.pop_back();
dq.front();
dq.back();
dq.size();
dq.empty()
vector<int> v;
v.push_back() //通过push_back来插入元素。
这里先挖个坑,图的遍历等下次再写
用堆栈实现图的深度优先(非递归)
用队列实现图的广度优先
图的递归与非递归深度优先:https://blog.youkuaiyun.com/weixin_41969587/article/details/82708219
图的邻接矩阵表示方法如下
#define MAXVERTEX 100 //图的最大顶点数
#define INFINITY 32767 //用有符号的int最大值表示无穷大
typedef char vertextype; //定义定点的存储信息为字符型
typedef int arctype; //定义边的权值为int型
//图的邻接矩阵的存储结构
typedef struct
{
vertextype vertex[MAXVERTEX]; //顶点表
arctype arc[MAXVERTEX][MAXVERTEX]; //邻接矩阵
int vertexnum; //图的当前顶点数
int arcnum; //图的当前边数
}MGraph;