1. 程式人生 > 實用技巧 >用棧求簡單算術表示式的值

用棧求簡單算術表示式的值

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