一、種類介紹
算術表達式有三種:前綴表達式、中綴表達式和后綴表達式。一般用的是中綴,比如1+1
,前后綴就是把操作符移到前面和后面,下面簡單介紹一下這三種表達式。
1、前綴表示法
前綴表示法又叫波蘭表示法,他的操作符置於操作數的前面(例:+ 1 2
),是波蘭數學家揚·武卡謝維奇1920年代引入的,用於簡化命題邏輯。因為我們一般認為操作符是在操作數中間的,所以在日常生活中用的不多,但在計算機科學領域占有一席之地。一般的表示法對計算機來說處理很麻煩,每個符號都要考慮優先級,還有括號這種會打亂優先級的存在,將使計算機花費大量的資源進行解析。而前綴表示法沒有優先級的概念,他是按順序處理的。舉個例子:9-2*3
這個式子,計算機需要先分析優先級,先乘后減,找到2*3
,再進行減操作;化成前綴表示法就是:- 9 * 2 3
,計算機可以依次讀取,操作符作用於后一個操作數,遇到減就是讓9減去后面的數,而跟着9的是乘,也就是說讓9減去乘的結果,這對計算機來說很簡單,按順序來就行了。
2、中綴表示法
這也就是我們一般的表示法,他的操作符置於操作數的中間(例:1 + 2
),前面也說過這種方法不容易被計算機解析,但他符合人們的普遍用法,許多編程語言也就用這種方法了。在中綴表示法中括號是必須有的,要不然運算順序會亂掉。
3、后綴表示法
后綴表示法又叫逆波蘭表示法,他的操作符置於操作數的后面(例:1 2 +
),他和前綴表示法都對計算機比較友好,但他很容易用堆棧解析,所以在計算機中用的很多。
他的解釋過程一般是:操作數入棧;遇到操作符時,操作數出棧,求值,將結果入棧;當一遍后,棧頂就是表達式的值。因此逆波蘭表達式的求值使用堆棧結構很容易實現,且能很快求值。
注意:逆波蘭記法並不是簡單的波蘭表達式的反轉。因為對於不滿足交換律的操作符,它的操作數寫法仍然是常規順序,如,波蘭記法/ 6 3
的逆波蘭記法是6 3 /
而不是3 6 /
;數字的數位寫法也是常規順序。
4、表示法間轉化
這里介紹一種簡單的中綴表達式轉化前后綴表達式的方法,比如這個式子:a+b*c-(d+e)
1.按照運算符的優先級對所有的運算單位加括號,式子變成:((a+(b*c))-(d+e))
2.前綴表達式,把運算符號移動到對應的括號前面,式子變成:-(+(a*(bc))+(de)),去掉括號:-+a*bc+de
3.后綴表達式,把運算符號移動到對應的括號后面,式子變成:((a(bc)*)+(de)+)-,去掉括號:abc*+de+-
下面的實例方法與上面的有所不同,因為情況更復雜了,請仔細推敲!
二、實例講解
1、表達式轉換
將以下中綴表達式:
3+4*2/(5-3)^2+3*(4-2)
COS(900-3*10*30)+123.45+30*30-0.45+TAN(0)
轉化為后綴表達式(RPN):
3,4,2,*,5,3,-,2,^,/,+,3,4,2,-,*,+
900,3,10,*,30,*,-,COS,123.45,+,30,30,*,+,0.45,-,0,TAN,+,
2、程序效果
3、編碼分析
編碼工作主要分為以下幾塊:
- 含有函數的中綴表達式轉后綴表達式
- 區分數字、函數名、運算符;
- 將字符串拆分為數組形式,方便入棧;
- 判斷運算符、函數的優先級,正確排列順序;
- 針對后綴表達式進行計算
- 區分數字、函數名、運算符的不同操作;
- 根據運算規則不同進行入棧、出棧和計算;
4、轉換流程
- 簡單算術表達式的中綴轉后綴
- 帶有函數名的表達式,中綴轉后綴
5、計算流程
- 簡單算術后綴表達式計算
- 帶有函數名的后綴表達式計算
6、實現代碼
/*是否為純數字。正則表達式實現*/
public static bool isNumber(string tmp)
{
return Regex.IsMatch(tmp, @"[0-9]+[.]{0,1}[0-9]*");
}
/*是否為需拆分的運算符+-*^/() */
public static bool isOp(string tmp)
{
bool bRet = false;
switch (tmp)
{
case "+":
case "-":
case "*":
case "/":
case "^":
case "(":
case ")":
bRet = true;
break;
default:
bRet = false;
break;
}
return bRet;
}
/*是否為一元函數名*/
public static bool isFunc(string tmp)
{
bool bRet = false;
switch (tmp)
{
case "SIN":
case "COS":
case "TAN":
case "SQRT":
bRet = true;
break;
default:
bRet = false;
break;
}
return bRet;
}
/*比較運算符及函數優先級。函數視作運算符進行操作。
返回值:1 表示 大於,-1 表示 小於,0 表示 相等 */
public static int compOper(string op1, string op2)
{
int iRet = 0;
Dictionary<string, int> dic = new Dictionary<string, int>();
dic.Add("+", 1);
dic.Add("-", 1);
dic.Add("*", 2);
dic.Add("/", 2);
dic.Add("^", 3);
dic.Add("SIN", 4);
dic.Add("COS", 4);
dic.Add("TAN", 4);
dic.Add("SQRT", 4);
dic.Add("(", 100);
dic.Add(")", 100);
if (dic[op1] > dic[op2])
iRet = 1;
else if (dic[op1] < dic[op2])
iRet = -1;
else
iRet = 0;
return iRet;
}
/*運算符、函數求值*/
public static double calValue(string op, string val1, string val2)
{
double dRet = 0.0d;
switch (op)
{
case "+":
dRet = double.Parse(val1) + double.Parse(val2);
break;
case "-":
dRet = double.Parse(val1) - double.Parse(val2);
break;
case "*":
dRet = double.Parse(val1) * double.Parse(val2);
break;
case "/":
if (double.Parse(val2) != 0)
dRet = double.Parse(val1) / double.Parse(val2);
else
MessageBox.Show("Error!");
break;
case "^":
dRet = Math.Pow(double.Parse(val1), double.Parse(val2));
break;
default:
break;
}
return dRet;
}
public static double calValue(string op, string val1)
{
double dRet = 0.0d;
switch (op)
{
case "SIN":
dRet = Math.Sin(double.Parse(val1));
break;
case "COS":
dRet = Math.Cos(double.Parse(val1));
break;
case "TAN":
dRet = Math.Tan(double.Parse(val1));
break;
case "SQRT":
if (double.Parse(val1) > 0)
dRet = Math.Sqrt(double.Parse(val1));
else
MessageBox.Show("Error!");
break;
default:
break;
}
return dRet;
}
/*按照=+-*^/()分隔出元素*/
public static string splitFunc(string tmp)
{
string sRet = tmp;
sRet = sRet.Replace("=", "\n=\n");
sRet = sRet.Replace("+", "\n+\n");
sRet = sRet.Replace("-", "\n-\n");
sRet = sRet.Replace("*", "\n*\n");
sRet = sRet.Replace("/", "\n/\n");
sRet = sRet.Replace("^", "\n^\n");
sRet = sRet.Replace("(", "\n(\n");
sRet = sRet.Replace(")", "\n)\n");
return sRet;
}
/*中綴表達式轉后綴表達式。tmp為已經添加分隔符的中綴表達式字符串*/
public static string midToRPN(string tmp)
{
string sRet = ""; //返回值
string[] strArr = splitFunc(tmp.ToUpper()).Split('\n'); //字符串數組,存放分隔后的中綴表達式元素
Stack<string> strStk = new Stack<string>(); //棧,用於臨時存放運算符和函數名
for (int i = 0; i < strArr.Length; i++)
{
if (string.IsNullOrEmpty(strArr[i])) //分隔后為空的元素剔除
continue;
else if (calString.isNumber(strArr[i])) //純數字直接入隊列
sRet += strArr[i] + ',';
else if (calString.isFunc(strArr[i])) //一元函數名直接入棧
strStk.Push(strArr[i]);
else if (calString.isOp(strArr[i])) //運算符特殊處理
{
if (strStk.Count != 0 && strStk.Peek() == "(" && strArr[i] != ")") //棧不為空,最上層為"(",則運算符直接入棧
{
strStk.Push(strArr[i]);
}
else if (strStk.Count != 0 && strArr[i] == ")") //棧不為空,遇")"則pop至"("為止
{
while (strStk.Peek() != "(")
sRet += strStk.Pop() + ',';
strStk.Pop();
if (strStk.Count != 0 && calString.isFunc(strStk.Peek())) //若"("后為一元函數名,則函數名也pop出
sRet += strStk.Pop() + ',';
}
else if (strStk.Count != 0 && calString.compOper(strArr[i], strStk.Peek()) == -1)
{ //棧不為空,運算符優先級小
while (strStk.Count != 0 && strStk.Peek() != "(" && calString.compOper(strArr[i], strStk.Peek()) == -1)
sRet += strStk.Pop() + ','; //則一直pop【存疑】
if (strStk.Count != 0) //pop至優先級不小於棧頂運算符則交換位置
sRet += strStk.Pop() + ','; //先pop
strStk.Push(strArr[i]); //再push
}
else if (strStk.Count != 0 && calString.compOper(strArr[i], strStk.Peek()) == 0)
{ //運算符優先級相同,先pop再push
sRet += strStk.Pop() + ',';
strStk.Push(strArr[i]);
}
else if (strStk.Count != 0 && calString.compOper(strArr[i], strStk.Peek()) == 1)
{ //運算符優先級大,直接入棧
strStk.Push(strArr[i]);
}
else //其他情況,入棧【存疑】
{
strStk.Push(strArr[i]);
}
}
}
while (strStk.Count > 0) //最后棧內元素全部pop出
{
sRet += strStk.Pop() + ',';
}
return sRet; //返回后綴表達式
}
/*根據傳入的后綴表達式,求值。tmp為含逗號分隔符的后綴表達式 */
public static double calRPN(string tmp)
{
double dRet = 0.0d;
string[] strArr = tmp.Split(',');
Stack<string> strStk = new Stack<string>();
for (int i = 0; i < strArr.Length - 1; i++)
{
if (isNumber(strArr[i])) //純數字入棧
strStk.Push(strArr[i]);
else if (isOp(strArr[i])) //二元運算符,pop兩個元素,計算值后壓入棧
strStk.Push(calValue(strStk.Pop(), strStk.Pop(), strArr[i]).ToString());
else if (isFunc(strArr[i])) //一元函數名,pop一個元素,計算后壓入棧
strStk.Push(calValue(strArr[i], strStk.Pop()).ToString());
}
dRet = double.Parse(strStk.Pop()); //取最后棧中元素作為結果值
return dRet;
}
/*調用部分代碼*/
private void btnTrans_Click(object sender, EventArgs e)
{
//中綴表達式轉后綴表達式
this.tbRPN.Text = calString.midToRPN(this.tbOrigin.Text);
}
private void btnCal_Click(object sender, EventArgs e)
{
//后綴表達式求值
double tmp;
try
{
tmp = calString.calRPN(this.tbRPN.Text);
this.tbRes.Text = tmp.ToString();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
this.tbRes.Text = "Error";
}
finally
{
}
}