中缀表达式向后缀表达式的转换

表达式

由数字、算符、数字分组符号(括号)、自由变量和约束变量等以能求得数值的有意义排列方法所得的组合。约束变量在表达式中已被指定数值,而自由变量则可以在表达式之外另行指定数值。(百度百科)

前缀表达式

也叫“波兰式”,没有括号的算术表达式。这种表达式将运算符写在前面,操作数写在后面。
举例:- a + b c 等价于 a - ( b + c )

中缀表达式

标准的表达式(多项式写法),是一种通用的算术或逻辑公式的表达方法。这种表达式就是平时所见的表达式的正常表达。
举例:a + b * c + ( d * e + f ) * g

后缀表达式

缀表达式的后缀记法,也叫逆波兰记法。这种表达式不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。
后缀表达式举例:a b c * + d e * f + g * + 等价于 a + b * c + ( d * e + f ) * g

前缀,中缀,后缀表达式区分

运算符与操作符的位置不同。前缀表达式操作符位于其相关操作数前,后缀表达式操作符位于其相关操作数之后,中缀表达式操作符位于其相关操作数中间。前缀表达式和后缀表达式不需要括号来表示其运算优先级。

中缀表达式方便于正常的观看和优先级计算,而计算机闭关没有运算符的优先级计算机制,而是按照表达式从左向右进行计算。相对于中缀表达式来说,计算机操作前缀表达式或后缀表达式更加方便。

本节将分析如何做中缀表达式到后缀表达式的转换。
如何将中缀表达式转换为后缀表达式呢?首先需要先理解中缀表达式的运算逻辑。

中缀运算逻辑

计算表达式:a + b + c
嗯…这个就不用说了,不论是自己计算还是计算机,直接从左到右计算即可
计算表达式:a * b * c
同样,从左到右依次计算
计算表达式:a * b + c
乘法的优先级高于加法,先算乘法,后算加法。
① 计算 a * b 记为 A
② 计算 A + c
虽然乘法的优先级高于加法,但由于该表达式 * 在前,+ 在后,所以看起来依然是从左向右依次计算
计算表达式: a + b * c
① 计算表达式 b * c,将其结果存为 A
② 计算表达式 a + A,得到最终结果
通过观察可以发现,明明是 + 在前,但由于 * 的优先级高于 +,还是先计算了 乘法。

中缀表达式转后缀表达式思想:

利用栈的LIFO(后进先出)的特性,调整表达式的运算顺序。具体操作为:依次读取中缀表达式的操作数和操作符,如果读取到的是操作数,直接加入到有序集合。如果读取到的是如“+”,“*”,“(”操作符,那我们从栈中弹出栈元素直到发现优先级更低的元素为止。一个例外是处理“)”的时候,否则我们绝不从栈中移走“(”。
如 a + b * c,依次读取操作数和操作符。读取“a”,加入到有序集合,读取“+”,此时栈为空,直接入栈,读取“b”,加入有序集合。此时:
在这里插入图片描述
接着 * 被读入,此时待查元素优先级高于栈顶元素,执行操作符入栈,接着读入c,加入到有序集合。此时:
在这里插入图片描述
表达式读取完毕,而有序队列中显然只有操作数没有操作符,接下来则按照优先级的顺序依次将栈内操作符写入到有序集合(这也是为什么使用栈存储操作符的原因,优先级高的先弹出也就先进行运算)

最后有序集合中按顺序即为中缀表达式的后缀记法。
同级操作符之间的栈内存储是什么样的呢?a + b + c 的后缀是 a b + c + 还是 a b c + + 呢?

那我们先来研究一下后缀表达式是如何进行计算的。

后缀表达式运算

还是以 a + b * c 举例。该表达式的后缀记法:a b c * +。
运算规则:
依次读取表达式的每个元素推入栈中,如果读取到操作符,则对栈顶的2个元素进行操作运算,将运算结果推入栈中,直到最后一个元素读取完毕。不出意外的话,此时栈有且只有一个元素,该元素即为表达式的最后结果。
故 a b c * + 的运算如下:
① 读取 a b c,a b c 依次入栈,
② 读取 *, c 出栈,b出栈做乘法运算,得到 A
③ 将 b * c 的结果A推入栈中,栈内元素 a A
④ 读取 +,A出栈,a出栈做加法运算,得到 B
⑤ 将 a + A 的结果 B 推入栈中,栈内元素 B
读取完毕,栈顶元素 B 即为最后结果。

为了方便理解,这里我们将 a + b + c替换为 a + b - c,研究其后缀是 a b + c - 或 a b - c + 或 a b c - + 或 a b c + -

  1. 分析 a b + c -
    ① a b 入栈
    ② +: b a 出栈,计算 a + b => A,栈内元素 A
    ③ c入栈,栈内元素 A c
    ④ -: c A出栈,计算 A - c => B,栈内元素 B
    读取完毕,显然这是我们所期望的先进行 a + b 的运算和,再计算 a + b - c的运算结果。
  2. 对 a b c + -进行简单分析
    ① a b c 入栈
    ② +: b c 出栈,计算 b + c => A,栈内 a A
    ③ -: A a 出栈,计算 a - A => B,栈内 B
    读取完毕,栈内元素 B 为计算最后结果。不过这样一分析,我们发现这运算结果是 a - (b+c)??显然是不正确的。

可以以同样的方式推一下 a b - c +计算的是 a - b + c,a b c - + 计算的是 a + (b-c)。虽然说 a b c - + 计算结果和预期结果一样,但我们更希望 a+b-c 先做加法,而非减法

由此总结一下,中缀表达式到后缀表达式,操作符的进栈规则:
① 待查操作符优先级高于栈顶元素:操作符入栈
② 待查操作符优先级不高于栈顶元素:从栈中弹出栈元素直到发现优先级更低的元素为止

结语

如果本文把你绕的糊涂了,嗯…我也没有办法,文字水平有限。当然本文都是以简单的例子来进行说明的,更复杂的还是需要自己理解,包括括号部分…
贴一下我自己写的代码,自己测试是没有问题的。如果代码部分各位有不错的优化方法或实现方式,欢迎评论分享。

   /**
     *
     * 中缀表达式 --> 后缀表达式
     *   将中缀表达式转为后缀表达式
     *
     *   测试用例: 
     *     中缀:4.99 * 1.06 + 5.99 + 6.99 * 1.06
     *     后缀:4.99 1.06 * 5.99 + 6.99 1.06 * +
     */
    private static String[] infixToSuffixArr(String stackStr) {

        String[] items = stackStr.trim().split(" ");  //重点不在这里,表达式的元素分割没有特别进行说明哈

        List<String> itemList = new ArrayList<>();
        Stack<Character> optStack = new Stack<>();

        for(String item : items) {

            if(isNumString(item)) {
                itemList.add(item);
                continue;
            }

            //待查元素优先级低于栈顶元素的运算符:栈顶元素加入队列,待查元素入栈,否则待查元素加入队列
            if(optStack.size() > 0) {

                char topOpt = optStack.pop();
                switch (item.charAt(0)) {
                    case '+':
                    case '-':
                        while(topOpt == '*' || topOpt == '/') {
                            itemList.add(String.valueOf(topOpt));
                            if(optStack.size() > 0) {
                                topOpt = optStack.pop();
                            } else {
                                itemList.remove(itemList.size()-1);
                                break;
                            }
                        }
                        if(topOpt == '(') {
                            optStack.push(topOpt);
                            optStack.push(item.charAt(0));
                        } else {
                            optStack.push(item.charAt(0));
                            itemList.add(String.valueOf(topOpt));
                        }
                        break;
                    case '*':
                    case '/':
                    case '(':
                        optStack.push(topOpt);
                        optStack.push(item.charAt(0));
                        break;
                    case ')':

                        //出栈直到第一个开放符号
                        while(topOpt != '(') {
                            itemList.add(String.valueOf(topOpt));
                            if(optStack.size() > 0) {
                                topOpt = optStack.pop();
                            } else {
                                throw new IllegalArgumentException();
                            }
                        }
                        break;
                }
            } else {
                optStack.push(item.charAt(0));
            }
        }//for

        while(optStack.size() > 0) {
            itemList.add(String.valueOf(optStack.pop()));
        }
        optStack = null;

        items = new String[itemList.size()];
        items = itemList.toArray(items);

        return items;
    }

测试结果:
中缀表达式转后缀表达式测试结果

顺便记录一下后缀表达式的计算

    /**
     * 栈的后缀表达式运算
     */
    private static double suffixCount(String[] items) {

        if(null == items || items.length <= 0) {
            return 0;
        }

        for (String item : items) {

            if(isNumString(item)) {
                stack.push(Double.parseDouble(item));
            } else {
                //出栈时,栈内元素至少为 2 个
                if(null == stack || stack.size() < 2) {
                    throw new IllegalArgumentException("使用双目运算符运算前,栈内元素至少为 2 个");
                }

                double behind = stack.pop();
                double prev = stack.pop();
                switch (item) {
                    case "+":
                        //do add
                        stack.push(prev + behind);
                        break;
                    case "-":
                        stack.push(prev - behind);
                        break;
                    case "*":
                        stack.push(prev * behind);
                        break;
                    case "/":
                        try {
                            stack.push(prev / behind);
                        } catch (ArithmeticException e) {
                            System.out.println("分母不能为 0");
                        }
                        break;
                }
            }//if
        }

        if(stack != null) {
            if(stack.size() == 1) {
                return stack.pop();
            }
            System.out.println("错误的表达式");
        }
        throw new EmptyStackException();
    }//stack
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值