Hello,大家好,国庆的第二天,带来的是数据结构中堆栈部分的后缀表达式,这也是一块有关栈的应用方面困扰了众多同学的一个大难题,今天就让我们一起解决这个难题
后缀表达式也称为逆波兰表达式,也就是将算术运算符放在操作数的后面
相信这部分是大家最难过的一道坎,让我们来一探究竟
首先,最重要的一点是你必须知道转换的规则
法则一:如果遇到一个运算符op以及左括号“(”,此时栈如果为空,则直接将其进栈
法则二:若是遇到了操作数num,就将其直接输出
法则三:如果栈不为空,则只有当op的优先级高于栈顶运算符优先级时才将其进栈,否则将栈顶op弹出
法则四:若当前op进栈时发现栈中有与之相同op,则出栈其一,再加当前op压入栈中
法则五:若遇到右括号,则开始弹出栈字符,直到左括号为止
法则六:只要当遇到右括号“)”时,才从栈中弹出左括号“(”,否则一直遍历
有了固定的发展,接下来就是具体的案例和分析
Part1
Part2
【5 + 8*2 + ( 3 * 9 + 6 )*4】
Part3
【5 + 8*2 + ( 3 * 9 + 6 )*4】
Part4
【5 + 8*2 + ( 3 * 9 + 6 )*4】
Part5
【5 + 8*2 + ( 3 * 9 + 6 )*4】
Part6
【5 + 8*2 + ( 3 * 9 + 6 )*4】
了解了堆栈的基本原理后,接下来就是代码环节,也是可视化计算的重要一趴
void trans(char* exp, char postexp[]) //将算术表达式exp转换成后缀表达式postexp
{
char e;
SqStack* Optr; //定义运算符栈
InitStack(Optr); //初始化运算符栈
int i = 0; //i作为postexp的下标
while (*exp != '\0') //exp表达式未扫描完时循环
{
switch (*exp)
{
case '(': //判定为左括号
Push(Optr, '('); //左括号进栈
exp++; //继续扫描其他字符
break;
case ')': //判定为右括号
Pop(Optr, e); //出栈元素e
while (e != '(') //不为'('时循环
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //继续出栈元素e
}
exp++; //继续扫描其他字符
break;
case '+': //判定为加或减号
case '-':
while (!StackEmpty(Optr)) //栈不空循环
{
GetTop(Optr, e); //取栈顶元素e
if (e != '(') //e不是'('
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //出栈元素e
}
else //e是'(时退出循环
break;
}
Push(Optr, *exp); //将'+'或'-'进栈
exp++; //继续扫描其他字符
break;
case '*': //判定为'*'或'/'号
case '/':
while (!StackEmpty(Optr)) //栈不空循环
{
GetTop(Optr, e); //取栈顶元素e
if (e == '*' || e == '/') //将栈顶'*'或'/'运算符出栈并存放到postexp中
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //出栈元素e
}
else //e为非'*'或'/'运算符时退出循环
break;
}
Push(Optr, *exp); //将'*'或'/'进栈
exp++; //继续扫描其他字符
break;
default: //处理数字字符
while (*exp >= '0' && *exp <= '9') //判定为数字
{
postexp[i++] = *exp;
exp++;
}
postexp[i++] = ' '; //用#标识一个数值串结束
}
}
while (!StackEmpty(Optr)) //此时exp扫描完毕,栈不空时循环
{
Pop(Optr, e); //出栈元素e
postexp[i++] = e; //将e存放到postexp中
}
postexp[i] = '\0'; //给postexp表达式添加结束标识
DestroyStack(Optr); //销毁栈
}
看完这串代码,不用猜,我知道你已经已经想点下浏览器中这个网页的【×】了。但是何妨不听我讲完再说
case '(': //判定为左括号
Push(Optr, '('); //左括号进栈
exp++; //继续扫描其他字符
break;
case ')': //判定为右括号
Pop(Optr, e); //出栈元素e
while (e != '(') //不为'('时循环
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //继续出栈元素e
}
exp++; //继续扫描其他字符
break;
case '+': //判定为加或减号
case '-':
while (!StackEmpty(Optr)) //栈不空循环
{
GetTop(Optr, e); //取栈顶元素e
if (e != '(') //e不是'('
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //出栈元素e
}
else //e是'(时退出循环
break;
}
Push(Optr, *exp); //将'+'或'-'进栈
exp++; //继续扫描其他字符
break;
case '*': //判定为'*'或'/'号
case '/':
while (!StackEmpty(Optr)) //栈不空循环
{
GetTop(Optr, e); //取栈顶元素e
if (e == '*' || e == '/') //将栈顶'*'或'/'运算符出栈并存放到postexp中
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //出栈元素e
}
else //e为非'*'或'/'运算符时退出循环
break;
}
Push(Optr, *exp); //将'*'或'/'进栈
exp++; //继续扫描其他字符
break;
default: //处理数字字符
while (*exp >= '0' && *exp <= '9') //判定为数字
{
postexp[i++] = *exp;
exp++;
}
postexp[i++] = ' '; //用空格标识一个数值串结束
while (!StackEmpty(Optr)) //此时exp扫描完毕,栈不空时循环
{
Pop(Optr, e); //出栈元素e
postexp[i++] = e; //将e存放到postexp中
}
postexp[i] = '\0'; //给postexp表达式添加结束标识
DestroyStack(Optr); //销毁栈
看完了上面的细致讲解,难道你还想退出吗,不想的话就继续跟着我来吧
将中缀表达式转换为后缀表达式后,只是完成了我们的第一步,也就是有了一个模型了,接下去就要将其细心雕刻直至完美
首先一样,我们要了解一下其运算规则
double compvalue(char* postexp) //计算后缀表达式的值
{
double d, a, b, c, e;
SqStack1* Opnd; //定义操作数栈
InitStack1(Opnd); //初始化操作数栈
while (*postexp != '\0') //postexp字符串未扫描完时循环
{
switch (*postexp)
{
case '+': //判定为'+'号
Pop1(Opnd, a); //出栈元素a
Pop1(Opnd, b); //出栈元素b
c = b + a; //计算c
Push1(Opnd, c); //将计算结果c进栈
break;
case '-': //判定为'-'号
Pop1(Opnd, a); //出栈元素a
Pop1(Opnd, b); //出栈元素b
c = b - a; //计算c
Push1(Opnd, c); //将计算结果c进栈
break;
case '*': //判定为'*'号
Pop1(Opnd, a); //出栈元素a
Pop1(Opnd, b); //出栈元素b
c = b * a; //计算c
Push1(Opnd, c); //将计算结果c进栈
break;
case '/': //判定为'/'号
Pop1(Opnd, a); //出栈元素a
Pop1(Opnd, b); //出栈元素b
if (a != 0)
{
c = b / a; //计算c
Push1(Opnd, c); //将计算结果c进栈
break;
}
else
{
printf("\n\t除零错误!\n");
exit(0); //异常退出
}
break;
default: //处理数字字符
d = 0; //将连续的数字字符转换成对应的数值存放到d中
while (*postexp >= '0' && *postexp <= '9') //判定为数字字符
{
d = 10 * d + *postexp - '0';
postexp++;
}
Push1(Opnd, d); //将数值d进栈
break;
}
postexp++; //继续处理其他字符
}
GetTop1(Opnd, e); //取栈顶元素e
DestroyStack1(Opnd); //销毁栈
return e; //返回e
}
首先,就是要在main函数中,传入对应的exp和postexp
char exp[] = "(56 - 20)/(4 + 2)"; //错误!!!
char exp[] = "(56-20)/(4+2)"; //正确!!!
char exp[] = "5+8*2+(3*9+6)*4";
char postexp[MaxSize];
trans(exp, postexp);
printf("中缀表达式为:%s\n", exp);
printf("后缀表达式为:%s\n", postexp);
printf("表达式的值为:%g\n", compvalue(postexp));
这就是最终的结果,看后缀表达式,和我们上面通过堆栈原理分析的完全吻合,大家算出来是这样吗
//求简单表达式的值
#include
#include
#define MaxSize 100
//---------------------------------------------------------
//--运算符栈基本运算---------------------------------------
//---------------------------------------------------------
typedef struct
{
char data[MaxSize]; //存放运算符
int top; //栈顶指针
} SqStack;
void InitStack(SqStack*& s) //初始化栈
{
s = (SqStack*)malloc(sizeof(SqStack));
s->top = -1;
}
void DestroyStack(SqStack*& s) //销毁栈
{
free(s);
}
bool StackEmpty(SqStack* s) //判断栈是否为空
{
return(s->top == -1);
}
bool Push(SqStack*& s, char e) //进栈元素e
{
if (s->top == MaxSize - 1)
return false;
s->top++;
s->data[s->top] = e;
return true;
}
bool Pop(SqStack*& s, char& e) //出栈元素e
{
if (s->top == -1)
return false;
e = s->data[s->top];
s->top--;
return true;
}
bool GetTop(SqStack* s, char& e) //取栈顶元素e
{
if (s->top == -1)
return false;
e = s->data[s->top];
return true;
}
//---------------------------------------------------------
void trans(char* exp, char postexp[]) //将算术表达式exp转换成后缀表达式postexp
{
char e;
SqStack* Optr; //定义运算符栈
InitStack(Optr); //初始化运算符栈
int i = 0; //i作为postexp的下标
while (*exp != '\0') //exp表达式未扫描完时循环
{
switch (*exp)
{
case '(': //判定为左括号
Push(Optr, '('); //左括号进栈
exp++; //继续扫描其他字符
break;
case ')': //判定为右括号
Pop(Optr, e); //出栈元素e
while (e != '(') //不为'('时循环
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //继续出栈元素e
}
exp++; //继续扫描其他字符
break;
case '+': //判定为加或减号
case '-':
while (!StackEmpty(Optr)) //栈不空循环
{
GetTop(Optr, e); //取栈顶元素e
if (e != '(') //e不是'('
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //出栈元素e
}
else //e是'(时退出循环
break;
}
Push(Optr, *exp); //将'+'或'-'进栈
exp++; //继续扫描其他字符
break;
case '*': //判定为'*'或'/'号
case '/':
while (!StackEmpty(Optr)) //栈不空循环
{
GetTop(Optr, e); //取栈顶元素e
if (e == '*' || e == '/') //将栈顶'*'或'/'运算符出栈并存放到postexp中
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //出栈元素e
}
else //e为非'*'或'/'运算符时退出循环
break;
}
Push(Optr, *exp); //将'*'或'/'进栈
exp++; //继续扫描其他字符
break;
default: //处理数字字符
while (*exp >= '0' && *exp <= '9') //判定为数字
{
postexp[i++] = *exp;
exp++;
}
postexp[i++] = ' '; //用空格标识一个数值串结束
}
}
while (!StackEmpty(Optr)) //此时exp扫描完毕,栈不空时循环
{
Pop(Optr, e); //出栈元素e
postexp[i++] = e; //将e存放到postexp中
}
postexp[i] = '\0'; //给postexp表达式添加结束标识
DestroyStack(Optr); //销毁栈
}
//---------------------------------------------------------
//--操作数栈基本运算---------------------------------------
//---------------------------------------------------------
typedef struct
{
double data[MaxSize]; //存放数值
int top; //栈顶指针
} SqStack1;
void InitStack1(SqStack1*& s) //初始化栈
{
s = (SqStack1*)malloc(sizeof(SqStack1));
s->top = -1;
}
void DestroyStack1(SqStack1*& s) //销毁栈
{
free(s);
}
bool StackEmpty1(SqStack1* s) //判断栈是否为空
{
return(s->top == -1);
}
bool Push1(SqStack1*& s, double e) //进栈元素e
{
if (s->top == MaxSize - 1)
return false;
s->top++;
s->data[s->top] = e;
return true;
}
bool Pop1(SqStack1*& s, double& e) //出栈元素e
{
if (s->top == -1)
return false;
e = s->data[s->top];
s->top--;
return true;
}
bool GetTop1(SqStack1* s, double& e) //取栈顶元素e
{
if (s->top == -1)
return false;
e = s->data[s->top];
return true;
}
//---------------------------------------------------------
double compvalue(char* postexp) //计算后缀表达式的值
{
double d, a, b, c, e;
SqStack1* Opnd; //定义操作数栈
InitStack1(Opnd); //初始化操作数栈
while (*postexp != '\0') //postexp字符串未扫描完时循环
{
switch (*postexp)
{
case '+': //判定为'+'号
Pop1(Opnd, a); //出栈元素a
Pop1(Opnd, b); //出栈元素b
c = b + a; //计算c
Push1(Opnd, c); //将计算结果c进栈
break;
case '-': //判定为'-'号
Pop1(Opnd, a); //出栈元素a
Pop1(Opnd, b); //出栈元素b
c = b - a; //计算c
Push1(Opnd, c); //将计算结果c进栈
break;
case '*': //判定为'*'号
Pop1(Opnd, a); //出栈元素a
Pop1(Opnd, b); //出栈元素b
c = b * a; //计算c
Push1(Opnd, c); //将计算结果c进栈
break;
case '/': //判定为'/'号
Pop1(Opnd, a); //出栈元素a
Pop1(Opnd, b); //出栈元素b
if (a != 0)
{
c = b / a; //计算c
Push1(Opnd, c); //将计算结果c进栈
break;
}
else
{
printf("\n\t除零错误!\n");
exit(0); //异常退出
}
break;
default: //处理数字字符
d = 0; //将连续的数字字符转换成对应的数值存放到d中
while (*postexp >= '0' && *postexp <= '9') //判定为数字字符
{
d = 10 * d + *postexp - '0';
postexp++;
}
Push1(Opnd, d); //将数值d进栈
break;
}
postexp++; //继续处理其他字符
}
GetTop1(Opnd, e); //取栈顶元素e
DestroyStack1(Opnd); //销毁栈
return e; //返回e
}
void test()
{
char exp[] = "5+8*2+(3*9+6)*4";
char postexp[MaxSize];
trans(exp, postexp);
printf("中缀表达式为:%s\n", exp);
printf("后缀表达式为:%s\n", postexp);
printf("表达式的值为:%g\n", compvalue(postexp));
}
int main(void)
{
test();
return 0;
}
明白了如何将一个中缀表达式转换为后缀表达式,并且清楚了如何求值。接下来让我们到力扣上去找道题去练练手吧
原题传送门——逆波兰表达式求值
根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入:tokens = [“2”,“1”,“+”,“3”,“*”]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = [“4”,“13”,“5”,“/”,“+”]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = [“10”,“6”,“9”,“3”,“+”,“-11”,““,”/“,””,“17”,“+”,“5”,“+”]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
温馨提示,微信手机端看不到
逆波兰表达式
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for(int i = 0;i < tokens.size(); ++i)
{
if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/")
{
int num1 = st.top(); st.pop();
int num2 = st.top(); st.pop();
if(tokens[i] == "+") st.push(num2 + num1);
if(tokens[i] == "-") st.push(num2 - num1);
if(tokens[i] == "*") st.push((long long)num2 * num1);
if(tokens[i] == "/") st.push(num2 / num1);
}
else
{
//若为数字字符,则将其转换为数字,方便后续运算
st.push(stoi(tokens[i]));
}
}
int ret = st.top();
st.pop();
return ret;
}
};
代码很简单,就是去遍历给出的这个字符串,判断其为加减乘数四个运算符,是的话就将栈中的头两个元素出栈,进行一个四则运算,但是这里要注意的是因为展示FILO,
你第一个进栈的,也就是第二个出栈的才是第一个操作符
你第二个进栈的,也就是你第一个出栈的是第二个操作符
这个逻辑一定要理清,否则会出现运算问题,可以看到这里的运算num2都是在num1的前面
若不是四个运算符,则将其转换为数字进栈即可,这里用到了一个比较偏僻的函数stoi(),它可以将一个字符转换为数字形式,大家不清楚的可以去了解一下
OK,这就是我们今天要讲解的内容,有关数据结构中堆栈应用方面——后缀表达式,你学废了吗