中序表达式转为后序表达式

📚“九层之台,起于垒土”。学习技术须脚踏实地。

☀️你要做冲出的黑马,而不是坠落的星星。

🔑前言

本文为算法(第四版)的题目记录,主要练习栈的操作。中序表达式转为后序表达式是栈的经典应用之一,对于初学者熟练栈的基本操作和理解栈的原理很用帮助。
这里推荐一款刷题、模拟面试神器,可助你斩获心仪大厂offer:点我免费刷题、模拟面试

表达式根据运算符所在位置可分为三种:

  • 前缀表达式(波兰式),如(- (+ 3 (* 2 4)) 1),Lisp语言就是使用的这种表示方法
  • 中缀表达式,如3+2*4-1,最适合人阅读的表示方法
  • 后缀表达式(逆波兰式),如3 2 4 * + 1 -,计算机处理起来比较方便

E.W.Dijkstra 在 20 世纪 60 年代发明了一个非常简单的算法来计算中缀表达式,需要维护两个栈,一个是运算符栈,另一个是操作数栈。

表达式由括号、运算符和操作数(数字)组成。我们根据以下 4 种情况从左到右逐个将它们送入栈处理:

  • 将操作数压入操作数栈;
  • 将运算符压入运算符栈;
  • 忽略左括号;
  • 在遇到右括号时,弹出一个运算符,弹出所需数量的操作数,并将运算符和操作数的运算结果压入操作数栈。

在处理完最后一个右括号之后,操作数栈上只会有一个值,它就是表达式的值。

这个算法是怎么保证运算结果的正确性的呢?
每遇到一个被括号包围的表达式,都用它的值来替代它,这个值与表达式的值相同,反复利用这个规律得到的最终值就是整个表达式的最终值。

下面是 算法(第四版) 的代码:

public class Evaluate
{
    public static void main(String[] args)
    {
        Stack<String> ops = new Stack<String>();
        Stack<Double> vals = new Stack<Double>();
        while (!StdIn.isEmpty())
        { // 读取字符,如果是运算符则压入栈
            String s = StdIn.readString();
            if (s.equals("(")) ;
            else if (s.equals("+")) ops.push(s);
            else if (s.equals("-")) ops.push(s);
            else if (s.equals("*")) ops.push(s);
            else if (s.equals("/")) ops.push(s);
            else if (s.equals(")"))
            { // 如果字符为 ")",弹出运算符和操作数,计算结果并压入栈
                String op = ops.pop();
                double v = vals.pop();
                if (op.equals("+")) v = vals.pop() + v;
                else if (op.equals("-")) v = vals.pop() - v;
                else if (op.equals("*")) v = vals.pop() * v;
                else if (op.equals("/")) v = vals.pop() / v;
                vals.push(v);
            } // 如果字符既非运算符也不是括号,将它作为 double 值压入栈
            else vals.push(Double.parseDouble(s));
        }
        StdOut.println(vals.pop());
    }
}

计算后缀表达式很简单,只需要维护一个栈,让数字依次进栈,遇到运算符时,就弹出栈顶两个元素,将计算结果入栈,如图:

为了把较难处理的中缀表达式转化为容易处理的后缀表达式,Dijsktra(怎么又是他)引入了一个使用栈的算法,称为调度场算法。

为什么要是用栈呢?因为当处理 a+b 时,不知道下一个运算符是 * 还是 +,所以要先 + 号放到栈中,等后面有足够信息了再把 + 放出来。

Wikipedia调度场算法示意图。
在这里插入图片描述
算法示意图,使用了3个空间。输入用符号代替,如果输入是一个数字则直接进输出队列,即图中 b),d),f),h)。如果输入是运算符,则压入操作符堆栈,即图中 c),e),但是,如果输入运算符的优先级低于或等于运算符栈顶的操作符优先级,则栈内元素进入输出队列,输入操作符压入运算符堆栈,即图中 g)。 最后,运算符堆栈内元素入输出队列,算法结束。

括号处理: 括号具有最高的优先级,所以遇到右括号时需要将括号内的所有运算符出栈。遇到左括号时要进栈,用来确定括号范围。

综上,可得调度场算法描述:

  1. 读到数字:直接输出;
  2. 读到一般运算符:如果栈顶的运算符优先级不低于该运算符,则输出栈顶运算符并使之出栈,直到栈空或不满足上述条件为止;然后入栈;
  3. 读到左括号:直接入栈;
  4. 读到右括号:输出栈顶运算符并使之出栈,直到栈顶为左括号为止;令左括号出栈。
  5. 当读入完毕时,依次输出并弹出栈顶运算符,直到栈被清空。

例题:算法(第四版)习题1.3.10 与 习题1.3.11,计算后缀表达式并利用后序表达式求值。

import edu.princeton.cs.algs4.Stack;
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;

import java.util.HashMap;
import java.util.LinkedList;

public class exercise1310 {
    private static boolean isNumeric(String s) {
        for (int i = 0; i < s.length(); ++i) {
            if (!Character.isDigit(s.charAt(i)))
                return false;
        }
        return true;
    }

    private static boolean isOperator(String s) {
        if (s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/") || s.equals("^"))
            return true;
        return false;
    }

    public static void EvaluatePostfix(LinkedList<String> S) {
        Stack<Double> stack = new Stack<>();
        for (String s : S) {
            if (isOperator(s)) {
                double a = stack.pop();
                double b = stack.pop();
                switch (s) {
                    case "+":
                        stack.push(b + a);
                        break;
                    case "-":
                        stack.push(b - a);
                        break;
                    case "*":
                        stack.push(b * a);
                        break;
                    default:
                        stack.push(b / a);
                        break;
                }
            } else {
                stack.push(Double.parseDouble(s));
            }

        }
        StdOut.println(stack.pop());
    }

    public static LinkedList<String> InfixToPostfix(String[] S) {
        LinkedList<String> result = new LinkedList<>();
        int i = 0;
        Stack<String> stack = new Stack<>();
        HashMap<String, Integer> operAdv = new HashMap<>();
        operAdv.put("+", 1);
        operAdv.put("-", 1);
        operAdv.put("*", 2);
        operAdv.put("/", 2);
        for (String s : S) {
            if (s.equals("(")) {
                stack.push(s);
                continue;
            }
            if (s.equals(")")) {
                while (!stack.isEmpty() && !stack.peek().equals("(")) {
                    result.add(stack.peek());
                    StdOut.print(stack.pop() + " ");
                }
                stack.pop();
                continue;
            }
            if (isNumeric(s)) {
                result.add(s);
                StdOut.print(s + " ");
                continue;
            }
            if (isOperator(s)) {
                while (!stack.isEmpty() && (isOperator(stack.peek()) && operAdv.get(stack.peek()) >= operAdv.get(s))) {
                    result.add(stack.peek());
                    StdOut.print(stack.pop() + " ");
                }
                stack.push(s);
            }
        }
        for (String s : stack) {
            result.add(s);
            StdOut.print(s + " ");
        }
        StdOut.println();
        return result;
    }

    public static void main(String[] args) {
        String express = StdIn.readAll();
        express = express.substring(0, express.length() - 2);
        String[] S = express.split(" ");
        LinkedList<String> strings = InfixToPostfix(S);
        EvaluatePostfix(strings);
    }
}

运算结果:

( 1234 + 4 + 5 ) * 3 * 10 / 5 + 3
^Z
1234 4 + 5 + 3 * 10 * 5 / 3 +
7461.0

( 2 + 4 ) / ( 4 * ( 3 - 1 ) * 10 ) + 123
^Z
2 4 + 4 3 1 - * 10 * / 123 +
123.075
  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的博仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值