zoukankan      html  css  js  c++  java
  • 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("=", "
    =
    ");
        sRet = sRet.Replace("+", "
    +
    ");
        sRet = sRet.Replace("-", "
    -
    ");
        sRet = sRet.Replace("*", "
    *
    ");
        sRet = sRet.Replace("/", "
    /
    ");
        sRet = sRet.Replace("^", "
    ^
    ");
        sRet = sRet.Replace("(", "
    (
    ");
        sRet = sRet.Replace(")", "
    )
    ");
        return sRet;
    }
     
    /*中缀表达式转后缀表达式。tmp为已经添加分隔符的中缀表达式字符串*/
    public static string midToRPN(string tmp)
    {
        string sRet = "";                                               //返回值
        string[] strArr = splitFunc(tmp.ToUpper()).Split('
    ');         //字符串数组,存放分隔后的中缀表达式元素
        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
        {
        }
    }
    
  • 相关阅读:
    DeepLearning.ai学习笔记(二)改善深层神经网络:超参数调试、正则化以及优化--Week2优化算法
    DeepLearning.ai学习笔记(二)改善深层神经网络:超参数调试、正则化以及优化--Week1深度学习的实用层面
    通俗理解决策树中的熵&条件熵&信息增益
    KNN实现手写数字识别
    softmax分类算法原理(用python实现)
    DeepLearning.ai学习笔记(一)神经网络和深度学习--Week4深层神经网络
    博客园自定义皮肤扁平化设计
    神经网络权重初始化问题
    OpenVirteX 安装
    从个人的角度谈谈本次GNTC大会的收获
  • 原文地址:https://www.cnblogs.com/zhaoshujie/p/15056435.html
Copyright © 2011-2022 走看看