数据结构-栈的应用之中缀表达式的计算

中缀表达式就是我们所熟悉的数学算式。如:3 + 6 - 18 / 23 + 3 * ( 26 - 16 / 2 ) / 2 + 2 - 20
我们的目标是要实现一个计算器,来根据中缀表达式计算表达式的值。表达式由数字和+ - * / ()组成。使用两个栈来实现,一个数值栈,一个符号栈。

在这里我们假设表达式都是正确的,并且数值与符号之间由空格隔开。在代码中不再判断表达式的格式是否正确。

实现思路

首先要确定的是,运算符是有优先级的。先来考虑没有括号的情况。
我们约定+ -运算符的优先级为1* /运算符的优先级为2

无括号情况

在没有括号时,我们只需要先计算优先级高的式子,再回过头来计算优先级低的式子即可。入下图所示

2efde3947d764494a0e71a1246c94a9epng

流程如下:

  1. 先分割表达式
  2. 依次遍历表达式
    • 如果是运算符,直接入栈
    • 如果是数字,先判断数值栈是否为空,如果为空,则直接入栈;若数值栈不为空,查看符号栈栈顶的符号的优先级,如果是2(* /),则取出数值栈栈顶元素和符号栈栈顶元素然后进行计算,并将计算结果重新入数值栈,如果优先级是1(+ -)则直接入栈。
  3. 遍历结束后,符号栈中就只剩下了+ -
  4. 遍历符号栈,从栈顶取出运算符,然后从数值栈中取出两个数值,然后进行运算,并将运算结果重新入栈。重复此操作,直到符号栈为空。

有括号的情况

有括号的情况会比无括号的情况复杂一些,我们需要将括号内的表达式看成一个完整的子表达式。遇到前括号就算是一个分界点,括号内的运算依然遵循,优先级高的先运算的原则,只是不再等待最后计算优先级低的运算。而是遇到后括号后就要开始符号栈的自顶而下的遍历。只到遇到前括号遍历结束。

遍历结束后还不能继续做表达式的遍历,而是应该查看符号栈栈顶的符号优先级是否为2。如果为2,则取出栈顶运算符,再从数值栈中取两个数值,然后进行运算,并将运算结果重新入数值栈。

重复以上步骤,直到符号栈栈顶运算符有限级为1或栈顶符号为(

代码实现

package com.codestd.study.stack;

import org.apache.commons.lang3.StringUtils;

import java.util.Stack;

/**
 * 中缀表达式
 *
 * @author jaune
 * @since 1.0.0
 */
public class InfixExpression {


    /**
     * 计算表达式的值
     * @param expression 表达式,数值与符号之间必须用空格隔开。
     */
    public int calculate(String expression) {
        String[] strings = StringUtils.split(expression, ' ');
        // 数值栈
        Stack<Integer> numStack = new Stack<>();
        // 符号栈
        Stack<String> operStack = new Stack<>();
        for (String s : strings) {
            if (isNumber(s)) { // 数值处理
                if (numStack.isEmpty()) {
                    // 如果数值栈为空,直接入栈
                    numStack.push(Integer.parseInt(s));
                } else if (isOpeningBracket(operStack.peek())) {
                    // 如果符号栈栈顶是前括号,直接入栈
                    numStack.push(Integer.parseInt(s));
                } else if (getPriority(operStack.peek()) == 2) {
                    // 如果符号栈栈顶的运算符优先级高,则取出符号栈栈顶元素,取出数值栈栈顶元素然后进行计算
                    // 并将计算结果重新入数值栈
                    int num1 = numStack.pop();
                    int num2 = Integer.parseInt(s);
                    int res = this.calc(num1, num2, operStack.pop());
                    numStack.push(res);
                } else {
                    // 其他情况直接入数值栈
                    numStack.push(Integer.parseInt(s));
                }
            } else if (isBracket(s)) { // 括号处理
                if (isOpeningBracket(s)) {
                    // 如果是前括号直接入符号栈
                    operStack.push(s);
                } else {
                    // 如果是后括号则完成括号内子式的计算
                    this.calcBracket(numStack, operStack);
                }
            } else if (isOperator(s)) { // 运算符处理
                operStack.push(s);
            }
        }

        // 依次取出符号栈和数值栈内的运算符和数值进行计算
        while (!operStack.isEmpty()) {
            int num2 = numStack.pop();
            int num1 = numStack.pop();
            int res = this.calc(num1, num2, operStack.pop());
            numStack.push(res);
        }

        return numStack.pop();

    }

    /**
     * 计算括号内的表达式
     */
    private void calcBracket(Stack<Integer> numStack, Stack<String> operStack) {
        // (12)
        if (isOpeningBracket(operStack.peek())) {
            operStack.pop();
            highPriorityCalc(numStack, operStack);
        } else {
            // 经过优先级高先计算的规则,括号内不会出现乘法和除法,所以顺序计算即可。
            int num2 = numStack.pop();
            int num1 = numStack.pop();
            int res = this.calc(num1, num2, operStack.pop());
            numStack.push(res);
            calcBracket(numStack, operStack);
        }
    }

    /**
     * 高优先级运算符的运算
     */
    private void highPriorityCalc(Stack<Integer> numStack, Stack<String> operStack) {
        if (this.isOperator(operStack.peek()) && this.getPriority(operStack.peek()) == 2) {
            int num2 = numStack.pop();
            int num1 = numStack.pop();
            int res = this.calc(num1, num2, operStack.pop());
            numStack.push(res);
            highPriorityCalc(numStack, operStack);
        }
    }

    private int calc(int num1, int num2, String s) {
        int res;
        switch (s) {
            case "+":
                res = num1 + num2;
                break;
            case "-":
                res = num1 - num2;
                break;
            case "/":
                res = num1 / num2;
                break;
            case "*":
                res = num1 * num2;
                break;
            default:
                throw new RuntimeException("不能识别的符号");
        }
        return res;
    }

    private boolean isNumber(String s) {
        return s.matches("\\d+");
    }

    private boolean isOperator(String s) {
        return "+".equals(s) || "-".equals(s) || "*".equals(s) || "/".equals(s);
    }

    private boolean isBracket(String s) {
        return "(".equals(s) || ")".equals(s);
    }

    private boolean isOpeningBracket(String s) {
        return "(".equals(s);
    }

    private int getPriority(String s) {
        if ("+".equals(s) || "-".equals(s)) {
            return 1;
        } else if ("*".equals(s) || "/".equals(s)) {
            return 2;
        } else {
            throw new RuntimeException("不识别此符号");
        }
    }
}

测试代码如下

public class InfixExpressionTest {

    @Test
    public void test() {
        String expression = "3 + 6 - 18 / 2";
        InfixExpression ie = new InfixExpression();
        int res = ie.calculate(expression);
        assertThat(res).isEqualTo(0);
    }

    @Test
    public void test2() {
        String expression = "3 + 3 * ( 26 - 16 / 2 ) / 2 + 2 - 20"; // 3+3*18/2-18=3+27-18=12
        InfixExpression ie = new InfixExpression();
        int res = ie.calculate(expression);
        assertThat(res).isEqualTo(12);
    }

}

以上代码已通过此测试代码。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容