用栈求简单算术表达式的值
用C#写了一个简易计算器,核心思路如下。查看项目源码请点击文末链接。
问题描述
这里限定的简单算术表达式求值问题是:用户输入一个包含“+”、“-”、“*”、“/”、正整数和圆括号的合法数学表达式,计算该表达式的运算结果。
数据组织
数学表达式exp采用字符数组表示,其中只含有“+”、”-“、“* ”、“/"、正整数和圆括号,为了方便,假设该表达式都是合法的数学表达式,例如,exp= "1+2*(4+12)”1;在设计相关算法中用到两个栈、一个运算符栈和一个运算数栈,均采用顺序栈存储结构,这两个栈的类型如下:
struct OpType//运算符栈类型
{
public char[] data;//存放运算符
public int top;//栈顶指针
}
struct ValueType//运算数栈类型
{
public double[] data;//存放运算符
public int top;//栈顶指针
}
设计相关算法
在算术表达式中,运算符位于两个操作数中间的表达式称为中缀表达式,例如,1+2*3就是一个中缀表达式。中缀表达式是最常用的一种表达式方式。对中缀表达式的运算一般遵循"先乘除,后加减,从左到右计算,先括号内,后括号外”的规则。因此,中缀表达式不仅要依赖运算符优先级,而且还要处理括号。
所谓后缀表达式,就是运算符在操作数的后面,如1+2* 3的后缀表达式为123* +。在后缀表达式中已考虑了运算符的优先级,没有括号,只有操作数和运算符。
对后缀表达式求值过程是:从左到右读人后缀表达式,若读入的是一个操作数,就将它入数值栈,若读入的是一个运算符op,就从数值栈中连续出栈两个元素(两个操作数),假设为x和y,计算x op y的值,并将计算结果入数值栈;对整个后缀表达式读入结束时,栈顶元素就是计算结果。
表达式的求值过程是:先将算术表达式转换成后缀表达式,然后对该后缀表达式求值。
假设用exp存放算术表达式,用字符数组postexp存放后缀表达式。设计如下求表达式值的类ExpressClass如下(后面求表达式值的方法均包含在该类中):
class ExpressClass
{
const int MaxSize=100;
pubic string exp;//存放中缀表达式
public char[] postexp;//存放后缀表达式
public int num;//postexp中的字符个数
public OpType op = new OpType();//运算符栈
public ValueType st = new ValueType();//运算数栈
public expressClass()//构造函数,用于栈等的初始化
{
postexp = new char[MaxSize];
pnum = 0;
op.data = new char[MaxSize];
op.top=-1;
st.data= new double[MaxSize];
st.top=-1;
}//表达式求值算法
}
将算术表达式转换成后缀表达式postexp的过程是:对于数字符,将其直接放到postexp中; 对于‘(’,将其直接进栈:对于‘)’,退栈运算符并将其放到postexp中,直到遇到‘(’。但不将‘(’放到postexp中。对于运算符\(op_2\),将其和栈顶运算符\(op_1\)的优先级进行比较,只有当\(op_2\)的优先级高于\(op_1\)的优先级时,才将\(op_2\)直接进栈,否则将栈中的‘(’,(如果有的话),以前的优先级等于或大于\(op_2\)的运算符均退栈并放到postexp中,再将\(op_2\)进栈。其描述如下:
while (若exp未读完)
{
从exp读取字符ch;
ch为数字:将后续的所有数字均依次存放到postexp中,并以字符'#'标志数值申结束;
ch为左括号'(':将'('进栈;
ch为右括号')':将op栈中'('以前的运算符依次出栈井存放到postxp中,再将'('退栈;
若ch的优先级高于栈顶运算符优先级,则将ch进栈;否则退栈并存入postexp中,再将ch进栈;
}
若字符串exp扫描完毕,则退栈op中的所有运算符并存放到postexp中。
在简单算术表达式中,只有'*'和'/'运算符的优先级高于'+'和'-'运算符的优先级。 所以,上述过程可进一.步改为:
while (若exp未读完)
{
从exp读取字符ch;
ch为数字:将后续的所有数字均依次存放到postexp中,并以字符'#'标志数值申结束;
ch为左括号'(':将'('进栈;
ch为右括号')':将op栈中'('以前的运算符依次出栈并存放到postxp中,再将'('退栈;
ch为'+'或'-':将op栈中'('以前的运算符依次出栈并存放到postxp中,再将'+'或'-'进栈;
ch为'*'或'/':将op栈中'('以前的'*'或'/'运算符出栈并存放到postxp中,再将'*'或'/'进栈;
}
若字符串exp扫描完毕,则退栈op中的所有运算符并存放到postexp中。
表达式(56-20)/(4+2)转换成后缀表达式的过程见下表
op栈 | postexp | 说明 |
---|---|---|
( | 遇到ch为'(',将此括号进栈op | |
( | 56# | 遇到ch为数字,将56存入数组exp中,并插入一个字符‘#’ |
(- | 56# | 遇到ch为‘-’,由于op中‘(’以前没有字符,则直接将ch进栈op中 |
(- | 56#20# | 遇到ch为数字,将20#存入数组exp中 |
56#20#- | 遇到ch为')',则将栈op中‘(’以前的字符依次删除并存入数组exp中,然后将‘(’删除 | |
/ | 56#20#- | 遇到ch为'/',将ch进栈op中 |
/( | 56#20#- | 遇到ch为‘(’,将此括号进栈op中 |
/( | 56#20#-4# | 遇到ch为数字,将4#存入数组exp中 |
/(+ | 56#20#-4# | 遇到ch为‘+’,由于op中‘(’以前没有字符,则直接将ch进栈op中 |
/(+ | 56#20#-4#2# | 遇到ch为数字,将2#存入数组exp中 |
/ | 56#20#-4#2#+ | 遇到ch为‘)’,则将栈op中‘(’以前的字符依次删除存入数组exp中,然后将‘(’删除 |
56#20#-4#2#+/ | str扫描完毕,则将栈op中的所有运算符依次弹出并存入数组exp中,然后再将ch存入数组exp中,得到后缀表达式 |
根据以上原理得到的Trans()算法如下:
public void Trans()//将算术表达式exp转换成后缀表达式postexp
{
int i=0,j=0;//i j分别作为exp和postexp的下标
char ch;
while(i<exp.Length)//表达式未扫描完时循环
{
ch=exp[i];
if(ch=='(')//判定为左括号
{ op.top++;
op.data[op.top]=ch;
}
else if(ch==')')//判定为右括号
{
while(op.top!=-1&&op.data[op.top]!='(')
{//将栈中'('前面的运算符退栈并存放到postexp中
postexp[j++] = op.data[op.top];
op.top--;
}
op.top--;//将'('退栈
}
else if(ch=='+'||ch=='-')//判断为加减号
{
while(op.top!=-1&&op.data[op.top]!='(')
{//将栈中'('前面的运算符退栈并存放到postexp中
postexp[j++] = op.data[op.top];
op.top--;
}
op.top++;op.data[op.top]=ch;//将'+'或'-'进栈
}
else if(ch=='*'||ch=='/')//判断为乘除号
{
while(op.top!=-1&&op.data[op.top]!='('&&(op.data[op.top]=='*'||op.data[op.top]=='/'))
{//将栈中'('前面的运算符退栈并存放到postexp中
postexp[j++] = op.data[op.top];
op.top--;
}
op.top++;op.data[op.top]=ch;//将'*'或'/'进栈
}
else//处理数字字符
{
while(ch>='0'&&ch<='9')//判断为数字
{
postexp[j++]=ch;
i++;
if(i<exp.Length) ch=exp[i];
else break;
}
i--;
postexp[j++]='#';//用#来标识一个数值串结束
}
i++;//继续处理其他字符
}
while(op.top!=-1)//退栈所有运算符并放到postexp中
{
postexp[j++]=op.data[op.top];
op.top--;
}
pnum=j;
}
在后缀表达式求值算法中哟啊用到一个数值栈st。后缀表达式求值过程如下:
while (若exp未读完)
{
从postexp读取字符ch;
ch为'+':从栈st中出栈两个数值a和b,计算c = a+b;将c进栈;
ch为'-':从栈st中出栈两个数值a和b,计算c = b-a;将c进栈;
ch为'*':从栈st中出栈两个数值a和b,计算c = b*a;将c进栈;
ch为'/':从栈st中出栈两个数值a和b,若a不为零,计算c = b/a;将c进栈;
ch为数字字符:将连续的数字串转换成数值d,将d进栈;
}
后缀表达式” 56#20#-4#2#+/ “的求值见下表
st栈 | 说明 |
---|---|
56 | 遇到56#,将56进栈 |
56,20 | 遇到20#,将20进栈 |
36 | 遇到‘-’,出栈两次,将56-20=36进栈 |
36,4 | 遇到4#,将4进栈 |
36,4,2 | 遇到2#,将2进栈 |
36,6 | 遇到‘+’,出栈两次,将4+2=6进栈 |
6 | 遇到‘/’,出栈两次,将36/6=6进栈 |
postexp扫描完毕,算法结束,栈顶数值6即为所求 |
根据上述计算原理得到的算法如下:
public bool GetValue(ref double v)//计算后缀表达式postexp的值
{
double a,b,c,d;
int i=0;
char ch;
while (i< pnum)//postexp字符串未扫描完时循环
{
ch= postexp[i];
switch (ch)
{
case'+': //判定为'+号
a=st. data[st. top]; //退栈取数值a
st. top--;
b=st. data[st. top]; //退栈取数值b
st. top--;
c=a+b; //计算c
st. top++;
st. data[st. top]=c;//将计算结果进栈
break;
case'-'://判定为-号
a=st. data[st. top];
st.top -- ;//退栈取数值a
b=st. data[st. top];
st.top--;//退栈取数值b
c=b- a;//计算c
st. top++;
st. data[st. top]=c;//将计算结果进栈
break;
case'*'://判定为'*'号
a=st. data[st. top];
st.top--;//退栈取数值a
b= st. data[st. top];
st.top-- ;//退栈取数值b
c=a* b;//计算c
st. top++;
st. data[st. top]=c;//将计算结果进栈
break;
case /'://判定为/号
a= st. data[st. top];st.top--;//退栈取数值a
b=st. data[st. top];st.top-- ;//退栈取数值b
if (a!=0)
{
c=b/ a;//计算c
st. top++;
st. data[st. top]=c;//将计算结果进栈
}
else return false;//除零错误,返回false
break;
default://处理数字字符
d=0;//将连续的数字符转换成数值存放到d中
while(ch>='0'&&ch<='9')
{
d= 10*d+ (ch- '0');
i++ ;
ch= postexp[ i];
}
st. top++ ;
st. data[st. top]= d;
break;
}
i++ ;//继续处理其他字符
}
v= st. data[st. top];
return true;
}