Pepperr Cat
文章7
标签5
分类4
栈与表达式计算

栈与表达式计算

[TOC]

栈与表达式计算

表达式

表达式是我们非常常见的运算的表示形式,有包括算数运算在内的,逻辑运算等各种表达式,根据运算符(operator)位置的不同,又可以分为一下三类:前缀、中缀和后缀表达式。其中,我们最常用也是最便于人类理解的,则是中缀表达式。

中缀表达式

顾名思义,中缀表达式即运算符位于运算对象中间的表达式,像1+1,2*5,3%(7+5)这种表达形式都是中缀表达式,对人类来说,其非常容易理解,但是设计优先级的问题对于机器来说则并不是很容易的进行计算。因此,人们另提出了前后缀两种表达式。

前缀与后缀表达式

同样的,前后缀表达式则是运算符分别位于运算对象前后的两种表达式,如同+1 2、*3 5、6 9-、4 5%等,这些则都是前、后缀表达式,其中,在计算机领域中,又以后缀表达式运用的最为广泛,其又被称为逆波兰表达式。但为何计算机对后缀表达式的计算理解更容易呢,这就涉及到后缀表达式的运算过程了。

后缀表达式的运算其实比较简单,读取到运算符后,就对前两个数进行运算,得到的结果放在还未进行运算的数后面,也就是说,位于后方的数,在读取到运算符后,反而优先计算,这中特殊的组织数据的方式和栈类似,于是我们可以用栈来实现后缀表达式的运算。

当读取到数字的时候,我们把数字直接压栈,当读取到符号后,就把栈顶的两个数弹栈,按照运算顺序对运算符进行计算,得到的结果压栈,反复进行这一过程,直到所有的运算符计算完成,剩下栈中的最后一个数就是表达式计算的结果。因此,前后缀表达式实际上并没有括号这一符号,因为其运算顺序已经在表达式中确定了。前缀表达式实际上过程一样,只是我们读取的过程要从后往前罢了。

为了 方便理解,这里举个例子:3 4 + 5 × 6 -,从右往左读取,读到的3 4压栈,然后读到 “+” 则把3 4 计算后得到7压栈,然后继续读到5,继续压栈,读到 “×” 则把7 5弹栈计算得到35后压栈,继续读到6压栈后读到 “-” ,弹栈计算后得到29即为最后结果。

因此,我们想实现中缀表达式的计算,如果能把中缀表达式转换为后缀表达式,就能通过栈很容易得到运算结果了。

中缀表达式求值的栈实现

分析及代码

终极代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#define MAXSIZE 100 //栈容量
typedef double DataType; //数据类型,可根据需要改变
typedef enum symbol //字符枚举变量,用于getsym函数判断读入内容为数字,操作符,等号,还是其他
{
NUM,
OPE,
EQU,
OTH
} SymType;
typedef enum operator //操作符枚举变量,用于入栈及根据优先级数列判断优先级
{
AND, //与
XOR, //疑惑
OR, //或
MORE, //大于
LESS, //小于
ADD,
MIN,
MUL,
DIV,
MOD, //取模
NOT, //非
LEFT, //左括号
RIGHT //右括号
} OpeType;
int prior[] = {0, -1, -2, 1, 1, 2, 2, 3, 3, 3, 4, 5, 5}; //优先级数列
union sym //联合变量,用于读取操作符或数字
{
DataType num;
OpeType op;
};
OpeType Ope_Stack[MAXSIZE]; //符号栈
int OpeTop = -1; //栈顶
DataType Num_Stack[MAXSIZE]; //数组栈
int NumTop = -1;
SymType GetSym(union sym *item); //读取数字或运算符函数,返回值为相应的枚举类型(NUM OPE EQU OHE)
void Push_Ope(OpeType op); //运算符压栈函数
void Push_Num(DataType num);
OpeType Pop_Ope(); //运算符压栈函数,返回值为弹栈元素枚举类型(ADD MIN MUL DIV)
DataType Pop_Num();
void operate(OpeType op); //操作函数,对无法压栈的字符串进行操作
void calculate(OpeType op); //运算函数,对数字栈顶两元素以传入运算符进行运算
int main()
{
union sym item; //储存数字或运算符
SymType s; //判断getsym返回类型
while ((s = GetSym(&item)) != EQU) //返回不为等号循环读取
{
if (s == NUM) //数字直接压栈
Push_Num(item.num);
else if (s == OPE) //符号导入操作函数进行操作
{
operate(item.op);
}
else //否则输入有问题
{
printf("Error Input!\n");
return 1;
}
}
while (OpeTop != -1) //读取操作完成后, 把符号栈剩余符号全部弹栈计算一遍
{
calculate(Pop_Ope());
}
if (NumTop == 0) //计算完成符号栈清空,数字栈只剩一个元素,弹栈输出即可
printf("%g\n", Pop_Num());
else //否则输入有问题
{
printf("Error Input!\n");
return 1;
}
}
SymType GetSym(union sym *item)
{
char c;
while ((c = getchar()) != '=')
{
if (c >= '0' && c <= '9')
{
for (item->num = 0; c >= '0' && c <= '9'; c = getchar()) //多位数字处理
{
item->num = item->num * 10 + c - '0';
}
ungetc(c, stdin); //退回缓冲区,防止读入符号丢失
return NUM; //返回数字枚举类型
}
else //匹配运算符
{
switch (c)
{
case '&':
item->op = AND;
return OPE;
case '^':
item->op = XOR;
return OPE;
case '|':
item->op = OR;
return OPE;
case '>':
item->op = MORE;
return OPE;
case '<':
item->op = LESS;
return OPE;
case '+':
item->op = ADD;
return OPE;
case '-':
item->op = MIN;
return OPE;
case '*':
item->op = MUL;
return OPE;
case '/':
item->op = DIV;
return OPE;
case '%':
item->op = MOD;
return OPE;
case '!':
item->op = NOT;
return OPE;
case '(':
item->op = LEFT;
return OPE;
case ')':
item->op = RIGHT;
return OPE;
case ' ': case '\t': case '\n': break; //空格直接跳
default:
return OTH; //读到怪东西,直接报错
}
}
}
return EQU; //读到等号,也就是表达式末尾
}
void Push_Ope(OpeType op)
{
if (OpeTop == MAXSIZE - 1) //满栈,无法入栈
{
printf("operator stack is full!\n");
exit(1);
}
Ope_Stack[++OpeTop] = op;
return;
}
void Push_Num(DataType num)
{
if (NumTop == MAXSIZE - 1)
{
printf("number stack is full!\n");
exit(1);
}
Num_Stack[++NumTop] = num;
return;
}
OpeType Pop_Ope()
{
if (OpeTop == -1) //栈空,无法弹栈
{
printf("Error Input!\n");
exit(1);
}
return Ope_Stack[OpeTop--];
}
DataType Pop_Num()
{
if (NumTop == -1)
{
printf("Error Input!\n");
exit(1);
}
return Num_Stack[NumTop--];
}
void operate(OpeType op)
{
OpeType t;
if (op == RIGHT) //如果读入右括号
{
while ((t = Pop_Ope()) != LEFT) //直到弹到左括号
calculate(t); //计算
}
else
{
while (OpeTop != -1 && prior[op] <= prior[Ope_Stack[OpeTop]] && Ope_Stack[OpeTop] != LEFT)
//弹栈计算直到,栈为空 或 栈顶优先级小于读入优先级 或 栈顶为左括号. 能够压栈后再压栈
calculate(Pop_Ope());
Push_Ope(op);
}
}
void calculate(OpeType op) //弹栈两数字,按照运算顺序计算,计算完成后把结果压栈
{
DataType temp; //临时变量储存栈顶元素,防止弹栈顺序问题导致计算先后出错
switch (op)
{
case AND:
Push_Num((int)Pop_Num() & (int)Pop_Num());
break;
case XOR:
Push_Num((int)Pop_Num() ^ (int)Pop_Num());
break;
case OR:
Push_Num((int)Pop_Num() | (int)Pop_Num());
break;
case MORE:
temp = Pop_Num();
Push_Num(Pop_Num() > temp);
break;
case LESS:
temp = Pop_Num();
Push_Num(Pop_Num() < temp);
break;
case ADD:
Push_Num(Pop_Num() + Pop_Num());
break;
case MIN:
temp = Pop_Num();
Push_Num(Pop_Num() - temp);
break;
case MUL:
Push_Num(Pop_Num() * Pop_Num());
break;
case DIV:
temp = Pop_Num();
Push_Num(Pop_Num() / temp);
break;
case MOD:
temp = Pop_Num();
Push_Num((int)Pop_Num() % (int)temp);
break;
case NOT:
Push_Num(!(int)Pop_Num());
break;
}
}
本文作者:Pepperr Cat
本文链接:https://peperrcat.github.io/2022/04/15/%E6%A0%88%E4%B8%8E%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%AE%A1%E7%AE%97/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可
×