利用栈实现中缀转化成后缀表达式并求值

后缀表达式


什么是后缀表达式
也许有的同学是第一次听到后缀表达式这个概念,那么什么是后缀表达式呢,结合名字你还会想到,有后缀表达式,那是不是也有前缀表达式呢?答案是肯定的,计算机的世界里一共有三种表达式,前缀、后缀、中缀。下面我们一起看一下这几个概念。

  1. 中缀表达式:中缀表达式是我们平时最常用的一种表达式,他的一般情形是:<操作数><操作符><操作数>,例如3 + 4
  2. 前缀表达式:前缀表达式顾名思义就是把操作符放到前面,例如+ 3 4
  3. 后缀表达式:那后缀表达式就明显了,肯定是操作符放后面,例如3 4 +

为什么要有后缀表达式
我们平时做计算是用的中缀表达式,这是一种便于我们人类理解阅读的表达方式。如果给定3 + 4 * (2 - 5)。我们知道先算括号里面的减法在和4相乘最后算加法。但是计算机不行啊,他没有这么聪明啊,他是怎么算的呢。他一般就是用这种数据结构来算的。那么如果用前缀表达式让计算机算,他就需要两个栈,一个用来存操作数operandStack,一个用来存操作符operatorStack。具体步骤分两步:

  1. 如果是操作数,入栈到operandStack
  2. 如果是操作符,需要和栈顶的符号做一个比较,如果当前操作符优先级比栈顶的符号优先级高,就直接入栈到operatorStack。如果比栈顶的符号优先级低或者相同,就需要需要从符号栈里面出栈一个符号去做计算,然后将计算的结果在入栈到operandStack。

后缀表达式具体的计算过程
假设我们计算一个表达式:42+5+67=,他的计算顺序可以是将42的值存为A1,然后将A1和5相加,在将结果存入A1,然后在将67的值存为A2,最后将A1和A2相加,并将结果放入A1。那我们可以将这种操作顺序写成这样:4 25+6 7+。这个写法就是我们说的后缀(postfix)或逆波兰(reverse polish)写法。这种写法的在计算机中可以用一个栈来实现:遇见一个数,把他放到栈中,在遇到一个运算符时,该运算符作用于从该栈弹出的两个数上,在将结果放入到栈中,例如:6 5 2 3 + 8 * + 3 + *

1.将6 5 2 3放入栈中
这里写图片描述
2.下面读到一个+号,所以3和2从栈中弹出,把他们的和5压入栈中
这里写图片描述
3.接着8入栈
这里写图片描述
4.然后 读到* ,8和5弹出,40进栈
这里写图片描述
重复这个过程,最终得出正确结果。这样下来计算一个后缀表达式花费的时间是O(N)。并且不需要知道任何优先级规则,只用一个栈就可以。

中缀到后缀的转化


我们平时接触到的都是中缀表达式,因此如何将他转化成后缀表达式是有必要的,下面我们一起来讨论一下。
表达式:a+b*c+(d*e+f)*g转化后是abc*+de*f+g*+
当读到一个数字的时候,放到输出中,操作符放入到一个栈中。具体的操作下面通过图说明:
1.首先,a被读入,放到输出中,然后 + 被读入,放到栈中,接下来,b读入放到输出中
这里写图片描述
2.接着读入,+ 没有 * 优先级高,+ 不输出, 进栈,c读入放到输出
这里写图片描述
3.接下来,+ 读入,+ 比 * 优先级底,所以将 * 从栈弹出到输出,弹出剩下的 +,该运算符和刚刚遇到的 + 优先级一样,然后将刚刚遇到的 + 压入栈中
这里写图片描述
4.接下里,最高优先级的 ( 读入,压入栈中,d 读入到输出中
这里写图片描述
5.继续,* 读入,压入栈中,e读入到输出
这里写图片描述
6.接续,+ 读入,比栈中的 * 优先级低,所以 * 出栈到输出,+ 压入栈中,f读入到输出
这里写图片描述
7.接下来,) 读入,因此将栈中的元素知道 ( 弹出
这里写图片描述
8.然后又读入 * ,压入栈中,g读入到输出
这里写图片描述
9.现在读入空,所以将栈中的符号全部弹出,知道变为空栈
这里写图片描述
这种转化只需要O(N)的时间并经过一趟输入后工作完成。

public class InfixToSuffix {

    private static final Map<Character,Integer> PRIORITY_MAP = new HashMap<>();
    private static final String OPERATOR = "*/+-()";
    
    static{
        PRIORITY_MAP.put('-', 1);
        PRIORITY_MAP.put('+', 1);
        PRIORITY_MAP.put('*', 2);
        PRIORITY_MAP.put('/', 2);
        PRIORITY_MAP.put('(', 0);
    }

    // 中缀表达式转换成后缀表达式
    private String toSuffix(String infix){
        List<String> suffix = new LinkedList<>();
        Stack<Character> stack = new Stack<>();
        
        // 用于记录字符长度  例如100*2,则记录的len为3 到时候截取字符串的前三位就是数字
        int len = 0;
        for(int i = 0; i < infix.length(); i++){
            char ch = infix.charAt(i);
            
            // 数字
            if (Character.isDigit(ch) || ch == '.') {
                len++;
            } else if (OPERATOR.indexOf(ch) != -1) {
                // 符号之前的可以截取下来做数字
                if (len > 0) {
                    suffix.add(infix.substring(i-len, i));
                    len = 0;
                }
                // 将左括号放入栈中
                if (ch == '(') {
                    stack.push(ch);
                    continue;
                }
                if (!stack.isEmpty()) {
                    int size = stack.size() - 1;
                    boolean flag = false;
                    
                    // 若当前ch为右括号,则栈里元素从栈顶一直弹出,直到弹出到左括号
                    while(size >= 0 && ch == ')' && stack.peek() != '('){
                        suffix.add(String.valueOf(stack.pop()));
                        size--;
                        flag = true;
                    }
                    
                    // 若取得不是()内的元素,并且当前栈顶元素的优先级>=对比元素 那就出栈插入队列
                    while(size >= 0 && !flag && PRIORITY_MAP.get(stack.peek()) >= PRIORITY_MAP.get(ch)){
                        suffix.add(String.valueOf(stack.pop()));
                        size--;
                    }
                }
                
                if (ch != ')') {
                    stack.push(ch);
                }else {
                    stack.pop();
                }
            } 
            // 如果已经走到了中缀表达式的最后一位
            if (i == infix.length() - 1) {
                if (len > 0) {
                    suffix.add(infix.substring(i - len + 1, i + 1));
                }
                
                int size = stack.size() - 1;
                // 一直将栈内 符号全部出栈并且加入队列中
                while(size >= 0) {
                    suffix.add(String.valueOf(stack.pop()));
                    size--;
                }
            }
        }
        return suffix.toString().substring(1, suffix.toString().length()-1);
    }

    // 根据后缀表达式计算结果
    private String calculate(String suffix){
        String [] arr = suffix.split(",");
        Stack<String> stack = new Stack<>();
        
        for (int i = 0; i < arr.length; i++) {
            switch (arr[i].trim()) {
            case "+": 
                double a = Double.parseDouble(stack.pop()) + Double.parseDouble(stack.pop());
                stack.push(String.valueOf(a));
                break;
            case "-": 
                double b = Double.parseDouble(stack.pop()) - Double.parseDouble(stack.pop());
                stack.push(String.valueOf(-b));
                break;
            case "*": 
                double c = Double.parseDouble(stack.pop()) * Double.parseDouble(stack.pop());
                stack.push(String.valueOf(c));
                break;
            case "/": 
                double d = Double.parseDouble(stack.pop()) / Double.parseDouble(stack.pop());
                stack.push(String.valueOf(d));
                break;
            default:
                stack.push(arr[i].trim());
                break;
            }
        }
        
        return stack.size() == 1 ? stack.pop() : "运算失败" ;
    }

    /**
     * 程序入口
     *
     * @param infix 中缀表达式
     * @return 计算结果
     */
    public String run(String infix) {
        String suffix = toSuffix(infix);
        return calculate(suffix);
    }

    public static void main(String[] args) {
        InfixToSuffix test = new InfixToSuffix();
        String result = test.run("20+5*(3-1)+9");
        System.out.println(result);
    }
}

代码参考:【java】中缀表达式转后缀表达式 java实现

  • 19
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

半__夏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值