C# 算術表達式求值(后綴法),看這一篇就夠了


一、種類介紹

算術表達式有三種:前綴表達式、中綴表達式和后綴表達式。一般用的是中綴,比如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、編碼分析

編碼工作主要分為以下幾塊:

  1. 含有函數的中綴表達式轉后綴表達式
    • 區分數字、函數名、運算符;
    • 將字符串拆分為數組形式,方便入棧;
    • 判斷運算符、函數的優先級,正確排列順序;
  2. 針對后綴表達式進行計算
    • 區分數字、函數名、運算符的不同操作;
    • 根據運算規則不同進行入棧、出棧和計算;

4、轉換流程

  1. 簡單算術表達式的中綴轉后綴

  1. 帶有函數名的表達式,中綴轉后綴

5、計算流程

  1. 簡單算術后綴表達式計算

  1. 帶有函數名的后綴表達式計算

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
    {
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM