栈介绍
栈可以理解为是一种受约束的线性表或链表,他们都是线性存储的,但是栈必须满足后进先出。与单链表类似,栈可以使用数组模拟和链式存储,数组模拟通常以数组下标0位栈底,而链式则以头结点作为栈顶。如下所示,左边为数组模拟栈的逻辑结构,右边为链式存储栈的逻辑结构,由于栈只进行头部操作,因此是否使用头结点效果一样。
栈的一切操作都与top指针有关。如下图1所示,当创建栈时初始化该数据结构,将top指针指向-1的位置如图2~6所示,分别表示使用栈进行入栈出栈操作,这两种操作都是让指针进行上下移动来完成此项操作的。
数组模拟栈代码
在进栈和出栈时,两者运算顺序是不同的,进栈时是先移动指针再入栈,否则会导致数组在下标为-1出赋值。初始化可以将top设置为为0,但对应的后序代码进栈出栈以及判断都需要配套。
#include <iostream>
using namespace std;
const int N = 3;
typedef struct Stack {
int sta[N];
int top;
}Stack;
void Init_Stack(Stack &s) //初始化
{
s.top = -1;
}
bool Push_Stack(Stack& s, int e) //入栈
{
if (s.top < N - 1)
{
s.sta[++ s.top] = e;
return true;
}
return false;
}
bool Empty_Stack(Stack& s)
{
if (s.top == -1) return true;
else return false;
}
bool Pop_Stack(Stack& s, int &res) //出栈
{
if (!Empty_Stack(s))
{
res = s.sta[s.top];
s.top--;
return true;
}
return false;
}
void Print_Stack(Stack& s)
{
if (Empty_Stack(s)) return;
for (int i = 0; i <= s.top; i++) cout << s.sta[i] << ' ';
puts("");
}
int main()
{
Stack s;
Init_Stack(s);
int op, res;
while (true)
{
cin >> op;
if (op == 1)
{
cin >> res;
bool flag = Push_Stack(s, res);
cout << flag << endl;
}
else if (op == 2)
{
bool flag = Pop_Stack(s, res);
if (flag == true)
cout << res << endl;
}
else if (op == 3)
{
Print_Stack(s);
}
else break;
}
return 0;
}
栈的应用
递归函数
栈的应用最广泛的就是函数的调用,递归函数是系统实现的栈不需要手写。如下代码时实现斐波那契数列的递归函数。
int a[100]
int dp(int n)
{
if (n == 1 || n == 2) return 1;
else return dp(n-1) + dp(n-2);
}
递归的思路与人类的思维方式相反,对于人而言第n个数是在n-1和n-2两个数已知的情况下退出的,而递归即使不知道n-1是多少可以找n-2与n-3……直到找到确定的数。因此同样的代码,如果写成循环更适合人脑去理解。
int a[100]
a[1]=a[2]=1;
for(int i=3;i<n;i++)
a[i]=a[i-1]+a[i-2];
DFS深度优先搜索
深度优先搜索是基于栈来实现的一种算法,其基本思想是一条路走到黑,从起点出发以一种方式直到找到结果或无法继续找结果才返回。DFS经典的处理问题:全排列、树的深度、找迷宫的出口、N皇后问题……以全排列为例,如下图所示,从根出发的第n层表示全排列后的第n个数字,层层往下去掉已选过的数字即可。
代码如下,开一个额外的数组q来记录哪些数字已被使用,递归玩需要还原。同样的数组p也是采用栈的思想来模拟的,当栈满了表示已经到叶子数即可输出。这里k是树的层数,而m是用来记录每层中的其中一个分支的。
#include<iostream>
#include<cstring>
using namespace std;
const int N=7;
int a[N],n,p[N],m=0;
bool q[N];
void dfs(int k)
{
if(k==n)
{
for(int i=0;i<n;i++) cout<<p[i]<<' ';
cout<<endl;
}
else
{
for(int i=0;i<n;i++)
{
if(!q[i])
{
p[m++]=a[i];
q[i]=1;
dfs(k+1);
q[i]=0;
m--;
}
}
}
}
int main()
{
cin>>n;
memset(q,0,sizeof(q));
for(int i=0;i<n;i++) a[i]=i+1;
dfs(0);
}
同样的迷宫问题也类似,定义运动的四个方向,起点相当于是四叉树的根,每一个节点下方都有四个方向,沿着一条路一直走下去即可。同时对边界进行判断是否出了迷宫的边界。
进制转换
在认为推到10进制转2进制时,让数值一直除二取余最后再余数倒读。除二取余的过程就是不断的进栈,余数倒读就是不断的出栈。
#include <stack>
#include <iostream>
using namespace std;
stack<int> s;
void conversion(int n)
{
while (n)
{
s.push(n % 2);
n /= 2;
}
while (s.size())
{
cout << s.top() << endl;
s.pop();
}
}
括号匹配的检验
使用栈在进栈之前进行判断,栈顶元素是否与进栈元素能匹配成一组括号,能则出栈;不能则入栈。
行编辑程序
个人理解为程序输入的缓冲区,输入数据为进栈,删除为出栈,回车表示遍历整个栈。
表达式求值
需要两个栈,一个记录符号,一个记录数据,就是用代码来实现平时的四则运算,可以参考下面的题。本题来自acwing,代码参考资料yxc,如果是课程题目需要付费才能提交代码一下链接仅供尝试。
3302. 表达式求值
给定一个表达式,其中运算符仅包含
+,-,*,/
(加 减 乘 整除),可能包含括号,请你求出表达式的最终值。注意:
- 数据保证给定的表达式合法。
- 题目保证符号
-
只作为减号出现,不会作为负号出现,例如,-1+2
,(2+2)*(-(1+1)+2)
之类表达式均不会出现。- 题目保证表达式中所有数字均为正整数。
- 题目保证表达式在中间计算过程以及结果中,均不超过 231−1231−1。
- 题目中的整除是指向 00 取整,也就是说对于大于 00 的结果向下取整,例如 5/3=15/3=1,对于小于 00 的结果向上取整,例如 5/(1−4)=−15/(1−4)=−1。
- C++和Java中的整除默认是向零取整;Python中的整除
//
默认向下取整,因此Python的eval()
函数中的整除也是向下取整,在本题中不能直接使用。输入格式
共一行,为给定表达式。
输出格式
共一行,为表达式的结果。
数据范围
表达式的长度不超过 105105。
输入样例:
(2+2)*(1+1)
输出样例:
8
#include<iostream>
#include<unordered_map>
#include<stack>
#include<string>
using namespace std;
stack<int>num;
stack<char>op;
unordered_map<char, int>Hash{ {'+',1},{'-',1},{'*',2},{'/',2} };
void eval()
{
int res;
int a = num.top();
num.pop();
int b = num.top();
num.pop();
char c=op.top();
op.pop();
if (c == '+') res = b + a;
else if (c == '-') res = b - a;
else if (c == '*') res = b * a;
else if (c == '/') res = b / a;
num.push(res);
}
int main()
{
string str;
cin >> str;
for (int i = 0; i<str.size(); i++)
{
if (isdigit(str[i])) //存入数字
{
int res = 0;
while (i < str.size() && isdigit(str[i]))
{
res = res * 10 + str[i] - '0';
i++;
}
num.push(res);
i--;
}
else if (str[i] == '(') op.push(str[i]);
else if (str[i] == ')')
{
while (op.top() != '(') //括号不匹配一直计算到匹配的括号为止
eval();
op.pop();
}
else
{
while (op.size()&&op.top()!='('&&Hash[op.top()]>=Hash[str[i]])
eval();
op.push(str[i]);
}
}
while (op.size()) eval(); //可能存在不含括号的结果或()+()
cout << num.top() << endl;
return 0;
}
共享栈
共享栈将数组的最大最小值作为两个栈的栈底,让两个栈顶向中间延伸。0号1号栈为空:top0=-1,top1=MaxSize;栈满:top1-top0=1。共享栈提高了空间利用率。
单调栈
单调栈是让每一个进栈的元素是栈里面最大或最小的一个元素。这样可以快速寻找每一个元素其左边第一个比它小或大的元素。如下案例:找每一元素左边第一个比它小的元素。
输入样例:
5 3 4 2 7 5
输出样例:
-1 3 -1 2 2
由于要找第一个比它小的数,因此只需要让栈顶元素是最大数即可,如果发现入栈的数小于栈顶元素就出栈至比它小即可,如果发现没有元素可以出栈了那么就是-1同时入栈。这样就可以保证栈里面的元素是恒单调的。
根据样例如下图所示,蓝色为栈里面的元素,灰色是出栈的元素。当2入栈时所有元素都比它大所有都出栈,当5进栈时7比它大就让他出栈,最后栈里面就存在2、5两个数。
下面代码中index表示该栈的指针,判断栈不为空且入栈元素不满足单调就持续出栈;只要判断出栈完毕后栈的状态来分支输出,最后将新元素入栈,如果要找右侧第一个比自己大或小的数只需要将数据倒读即可。代码参考自yxc。
#include<iostream>
using namespace std;
const int N=100010;
int a[N],index;
int main()
{
int n;
cin>>n;
while(n--)
{
int x; cin>>x;
while(index&&x<=a[index-1]) index--;
if(index) cout<<a[index-1]<<' ';
else cout<<-1<<' ';
a[index++]=x;
}
return 0;
}