用栈求简单算术表达式的值

用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;
    
}

点击查看项目源码

原文地址:https://www.cnblogs.com/gylic/p/14165635.html