开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第12天,点击查看活动详情
一、栈的定义与操作
1.顺序栈
1.定义
1.类型定义
typedef struct ssNode {
int data[N];
int top;//栈顶下标
ssNode():top(-1){}
}seqStack;
当top=-1表示空栈,当top=N-1表示栈满
2.操作
1.栈空
bool empty(seqStack ss) {
return ss.top == -1;
}
2.入栈
void push(seqStack& ss, int x) {
if (ss.top == N - 1) {
cout << "栈满" << endl;
return;
}
ss.data[++ss.top] = x;
}
3.获取栈顶元素
int top(seqStack ss){
if(empty(ss)){
cout<<"栈空"<<endl;
return 0;
}
return ss.data[ss.top];
}
4.出栈
void pop(seqStack& ss) {
if (empty(ss)) {
cout << "栈空" << endl;
return;
}
--ss.top;//将top下移一位
}
5.遍历
void ss_traverse(seqStack ss) {
while (!empty(ss)) {
cout << top(ss) << " ";
pop(ss);
}
cout << endl;
}
2.链栈
1.定义
1.类型定义
typedef struct csNode {
int data;
csNode* next;
csNode() :next(NULL) {}
}*chainStack;
2.操作
1.判断栈空
bool empty(chainStack cs) {
return cs->next == NULL;
}
2.入栈
void push(chainStack cs, int x) {
chainStack q = new csNode;//创建新节点
q->data = x;//赋值
q->next = cs->next;
cs->next = q;//重新定义cs的链域
}
3.获取栈顶元素
int top(chainStack cs) {
if (empty(cs))return 0;
return cs->next->data;
}
4.出栈
void pop(chainStack cs) {
if (empty(cs))return;
chainStack q = cs->next;
cs->next = q->next;
delete q;
q = NULL;
}
5.遍历
void cs_traverse(chainStack cs) {
while (!empty(cs)) {
cout << top(cs) << " ";
pop(cs);
}
cout << endl;
}
二、STL中的栈
1.定义
1.头文件:#include<stack>
2.注:对于适配器容器,默认底层容器为deque
stack<int>stk;//创建stack对象,底层容器为deque,栈中元素类型为int
stack<int, vector<int>>stk1;//创建stack对象,底层容器为vector
2.操作
int x;
q.push(x); //将x压入栈顶
q.top(); //返回栈顶的元素
q.pop(); //删除栈顶的元素
q.size(); //返回栈中元素的个数
q.empty(); //检查栈是否为空,若为空返回true,否则返回false
三、栈的应用
1.括号匹配
1.问题:根据输入的字符串,判断括号是否合法
2.括号的合法性:
-
每一个左括号的右端都有一个同类型的右括号与他匹配
-
每一个右括号的左端都有一个同类型的左括号与它匹配
-
两对不同类型括号之间不能骑那套
3.算法步骤
-
创建一个元素类型为字符型的栈stk
-
依次检查str中的每一个字符
str[i],如果str[i]为左括号,则将str[i]入栈;如果为右括号,则检查栈顶元素,分为
1.如果栈顶元素与str[i]同类型的左括号,则出栈,继续考虑str的下一个字符str[i+1]
2.如果栈为空或栈顶元素为与str[i]不同类型的左括号,则可以确定str中的括号不合法,检查结束 -
当str的所有元素都检查完毕时,检查stk,如果stk为空,则str中的括号合法;否则,str中的括号不合法
bool check_brackets(string str){
int i,sz=str.size();
stack<char>stk;
for(int i=0;i<sz;++i){
if(str[i]=='('||str[i]=='[')stk.push(str[i]);//左括号入栈
else{
if(str[i]!=')'&&str[i]!=']')continue;//跳过非括号字符
if(stk.empty())return false;//栈为空,不合法
char ch=stk.top();//获取栈顶元素
if(str[i]==')'&&ch!='('||str[i]==']'&&ch!='[')
return false;//栈顶元素与当前括号不匹配,不合法
stk.pop();//出栈
}
}
if(!stk.empty())return false;//栈为空,不合法
return true;
}
2.算术表达式求值
1.功能:求带括号的四则运算的计算结果,假设操作数都是整数,且括号只有小括号,并假设算术表达式中的运算和括号合法
2.算法步骤
-
建立一个double类型的栈opers(操作数栈)和char类型的栈ops(运算符栈)。依次考虑exp的每一个字符,假设当前考虑的字符为exp[i],则分为以下几种情况
1.如果exp[i]为数字,将exp[i]及其之后的连续数字组装为一个整数并加入操作数栈opers
2.如果exp[i]为左括号,则直接将exp[i]加入运算符栈ops
3.如果exp[i]为''或'/',则考虑ops的栈顶元素op,如果op为''或'/',则处理op的运算结果,并继续考虑ops的栈顶元素op,直到栈ops为空或op不为'*'或'/'为止。最后将exp[i]加入运算符栈ops。这里,处理op的运算结果是指:从操作数栈opers中取出两个元素v1,v2(先取栈顶元素,然后出栈,第一次去出的元素为v1,第二次取出的元素为v2),计算(注意,v2为左操作数,v1为右操作数),将计算结果加入操作数栈opers,并将op从ops中移除
4.如果exp[i]为'+'或'-',考虑ops的栈顶元素op,如果op不为'(',则处理op的运算结果,并继续考虑ops的栈顶元素op,直到栈ops为空或op为'('。最后将exp[i]加入运算符栈ops
5.如果exp[i]为')',考虑ops的栈顶元素op,如果op不为'(',则处理op的运算结果,并继续考虑ops的栈顶元素op,直到op为'(',最后将op出栈 -
当exp处理完毕,考虑ops的栈顶元素op,处理op的运算结果,并继续考虑op的栈顶元素,直到ops为空
-
此时栈opers中只有一个元素,为最终运算结果
//处理ops的栈顶op的运算结果:从栈opers中取两个数据v1,v,计算v op v1的结果并返回
double cal(stack<double>& opers, stack<char>& ops) {
double v1, v, ret;
v1 = opers.top(), opers.pop();//右操作数
v = opers.top(), opers.pop();//左操作数
char op = ops.top();
ops.pop();//运算符
switch (op) {
case '+':
ret = v + v1;
break;
case '-':
ret = v - v1;
break;
case '*':
ret = v * v1;
break;
case '/':
if (v1 == 0)exit(0);//分母为0
ret = v / v1;
break;
}
opers.push(ret);//计算结果入栈
}
//算术表达式求值
double calculate(string exp) {
stack<double>opers;
stack<char>ops;
int i, sz = exp.size(), v;
for (i = 0; i < sz; i++) {
if (isdigit(exp[i])) {//exp为数字,将其后连续的数字组装为一个整数
v = exp[i] - '0';
while (++i < sz && isdigit(exp[i]))
v = v * 10 + exp[i] - '0';
opers.push(v);//将组装的整数入栈
--i;
}
else if (exp[i] == '(')ops.push(exp[i]);//将左括号入栈
else if (exp[i] == '*' || exp[i] == '/') {
while (!ops.empty() && (ops.top() == '*' || ops.top() == '/'))
cal(opers, ops);
ops.push(exp[i]);
}
else if (exp[i] == '+' || exp[i] == '-' || exp[i] == ')') {//exp为加号、减号或右括号,消除栈顶的加减乘除
while (!ops.empty() && ops.top() != '(')
cal(opers, ops);
if (exp[i] == ')')ops.pop();//exp[i]为右括号,ops栈顶一定为'(',出栈
else ops.push(exp[i]);
}
}
while (!ops.empty())cal(opers, ops);//处理栈ops中的剩余运算符
return opers.top();//返回计算结果
}
四、单调栈
1.定义
1.定义:栈中的元素始终严格单调
2.递增栈:从栈顶到栈底的元素单调递增的单调栈
3.递减栈:从栈顶到栈底的元素单调递减的单调栈
2.操作
1.入栈和出栈的限制条件
-
如果栈空或
a[i]大于栈顶元素,则a[i]入栈 -
如果a[i]小于栈顶元素,则出栈,知道a[i]大于栈顶元素或栈空,然后将a[i]入栈
2.处理问题
-
利用递减栈可以求a[i]左端比a[i]小且离a[i]最近的元素,利用递增栈可以求a[i]左端第一个比a[i]大的元素,利用递增栈可以求a[i]左端第一个比a[i]大的元素
-
利用递减栈可以求a[i]左端与a[i]相邻且连续比a[i]大的元素个数,利用递增栈可以求a[i]左邻比其小的第一个元素个数
3.问题
1.求给定序列中每一个元素左端比其小的第一个元素
1.功能:对于序列a,求每一个元素a[i]左端比a[i]小的第一个元素,如果不存在,则为-1
2.算法步骤:
-
定义递减栈stk,依次考虑a中的每一个元素a[i]
-
如果stk为空,则输出-1,如果
a[i]>stk.top(),则栈顶元素为第一个比a[i]小的元素,直接输出stk.top(),a[i]入栈 -
如果
a[i]≤stk.top(),出栈,知道a[i]>stk.top() -
重复步骤2,3,知道所有元素都考虑完毕
//输出数组a的每一个元素的左邻比其小的值
void left_smaller(int a[], int n) {
stack<int>stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && a[i] <= stk.top())//a[i]不大于栈顶元素,出栈
stk.pop();
if (stk.empty())cout << -1 << ' ';//栈为空,a[i]左端不存在比其小的数
else cout << stk.top() << " ";//栈不空,栈顶即为a[i]左端第一个比其小的数
stk.push(a[i]);
}
}
//输出数组a的每一个元素的左邻比其大的元素的个数
void left_bigger(int a[], int n) {
stack<int>stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && a[i] <= a[stk.top()])stk.pop();
if (stk.empty())cout << i << " ";//栈为空,a[i]之前的元素都比a[i]大
else cout << i - stk.top() - 1 << ' ';//栈不空,a的下标在区间(i,stk.top())中的元素都比a[i]大
stk.push(i);//下标入栈
}
}