數(shù)據(jù)結(jié)構(gòu)-棧的應(yīng)用之中綴表達(dá)式的計(jì)算

中綴表達(dá)式就是我們所熟悉的數(shù)學(xué)算式碎浇。如:3 + 6 - 18 / 23 + 3 * ( 26 - 16 / 2 ) / 2 + 2 - 20
我們的目標(biāo)是要實(shí)現(xiàn)一個(gè)計(jì)算器麦射,來根據(jù)中綴表達(dá)式計(jì)算表達(dá)式的值捶惜。表達(dá)式由數(shù)字和+ - * / ()組成。使用兩個(gè)棧來實(shí)現(xiàn),一個(gè)數(shù)值棧得封,一個(gè)符號(hào)棧埋心。

在這里我們假設(shè)表達(dá)式都是正確的,并且數(shù)值與符號(hào)之間由空格隔開忙上。在代碼中不再判斷表達(dá)式的格式是否正確拷呆。

實(shí)現(xiàn)思路

首先要確定的是,運(yùn)算符是有優(yōu)先級(jí)的疫粥。先來考慮沒有括號(hào)的情況茬斧。
我們約定+ -運(yùn)算符的優(yōu)先級(jí)為1* /運(yùn)算符的優(yōu)先級(jí)為2梗逮,

無括號(hào)情況

在沒有括號(hào)時(shí)项秉,我們只需要先計(jì)算優(yōu)先級(jí)高的式子,再回過頭來計(jì)算優(yōu)先級(jí)低的式子即可慷彤。入下圖所示

2efde3947d764494a0e71a1246c94a9epng

流程如下:

  1. 先分割表達(dá)式
  2. 依次遍歷表達(dá)式
    • 如果是運(yùn)算符娄蔼,直接入棧
    • 如果是數(shù)字,先判斷數(shù)值棧是否為空底哗,如果為空岁诉,則直接入棧;若數(shù)值棧不為空跋选,查看符號(hào)棧棧頂?shù)姆?hào)的優(yōu)先級(jí)涕癣,如果是2(* /),則取出數(shù)值棧棧頂元素和符號(hào)棧棧頂元素然后進(jìn)行計(jì)算前标,并將計(jì)算結(jié)果重新入數(shù)值棧坠韩,如果優(yōu)先級(jí)是1(+ -)則直接入棧。
  3. 遍歷結(jié)束后炼列,符號(hào)棧中就只剩下了+ -同眯。
  4. 遍歷符號(hào)棧,從棧頂取出運(yùn)算符唯鸭,然后從數(shù)值棧中取出兩個(gè)數(shù)值须蜗,然后進(jìn)行運(yùn)算,并將運(yùn)算結(jié)果重新入棧。重復(fù)此操作明肮,直到符號(hào)棧為空菱农。

有括號(hào)的情況

有括號(hào)的情況會(huì)比無括號(hào)的情況復(fù)雜一些,我們需要將括號(hào)內(nèi)的表達(dá)式看成一個(gè)完整的子表達(dá)式柿估。遇到前括號(hào)就算是一個(gè)分界點(diǎn)循未,括號(hào)內(nèi)的運(yùn)算依然遵循,優(yōu)先級(jí)高的先運(yùn)算的原則秫舌,只是不再等待最后計(jì)算優(yōu)先級(jí)低的運(yùn)算的妖。而是遇到后括號(hào)后就要開始符號(hào)棧的自頂而下的遍歷。只到遇到前括號(hào)遍歷結(jié)束足陨。

遍歷結(jié)束后還不能繼續(xù)做表達(dá)式的遍歷嫂粟,而是應(yīng)該查看符號(hào)棧棧頂?shù)姆?hào)優(yōu)先級(jí)是否為2。如果為2墨缘,則取出棧頂運(yùn)算符星虹,再?gòu)臄?shù)值棧中取兩個(gè)數(shù)值,然后進(jìn)行運(yùn)算镊讼,并將運(yùn)算結(jié)果重新入數(shù)值棧宽涌。

重復(fù)以上步驟,直到符號(hào)棧棧頂運(yùn)算符有限級(jí)為1或棧頂符號(hào)為(蝶棋。

代碼實(shí)現(xiàn)

package com.codestd.study.stack;

import org.apache.commons.lang3.StringUtils;

import java.util.Stack;

/**
 * 中綴表達(dá)式
 *
 * @author jaune
 * @since 1.0.0
 */
public class InfixExpression {


    /**
     * 計(jì)算表達(dá)式的值
     * @param expression 表達(dá)式卸亮,數(shù)值與符號(hào)之間必須用空格隔開。
     */
    public int calculate(String expression) {
        String[] strings = StringUtils.split(expression, ' ');
        // 數(shù)值棧
        Stack<Integer> numStack = new Stack<>();
        // 符號(hào)棧
        Stack<String> operStack = new Stack<>();
        for (String s : strings) {
            if (isNumber(s)) { // 數(shù)值處理
                if (numStack.isEmpty()) {
                    // 如果數(shù)值棧為空玩裙,直接入棧
                    numStack.push(Integer.parseInt(s));
                } else if (isOpeningBracket(operStack.peek())) {
                    // 如果符號(hào)棧棧頂是前括號(hào)嫡良,直接入棧
                    numStack.push(Integer.parseInt(s));
                } else if (getPriority(operStack.peek()) == 2) {
                    // 如果符號(hào)棧棧頂?shù)倪\(yùn)算符優(yōu)先級(jí)高,則取出符號(hào)棧棧頂元素献酗,取出數(shù)值棧棧頂元素然后進(jìn)行計(jì)算
                    // 并將計(jì)算結(jié)果重新入數(shù)值棧
                    int num1 = numStack.pop();
                    int num2 = Integer.parseInt(s);
                    int res = this.calc(num1, num2, operStack.pop());
                    numStack.push(res);
                } else {
                    // 其他情況直接入數(shù)值棧
                    numStack.push(Integer.parseInt(s));
                }
            } else if (isBracket(s)) { // 括號(hào)處理
                if (isOpeningBracket(s)) {
                    // 如果是前括號(hào)直接入符號(hào)棧
                    operStack.push(s);
                } else {
                    // 如果是后括號(hào)則完成括號(hào)內(nèi)子式的計(jì)算
                    this.calcBracket(numStack, operStack);
                }
            } else if (isOperator(s)) { // 運(yùn)算符處理
                operStack.push(s);
            }
        }

        // 依次取出符號(hào)棧和數(shù)值棧內(nèi)的運(yùn)算符和數(shù)值進(jìn)行計(jì)算
        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();

    }

    /**
     * 計(jì)算括號(hào)內(nèi)的表達(dá)式
     */
    private void calcBracket(Stack<Integer> numStack, Stack<String> operStack) {
        // (12)
        if (isOpeningBracket(operStack.peek())) {
            operStack.pop();
            highPriorityCalc(numStack, operStack);
        } else {
            // 經(jīng)過優(yōu)先級(jí)高先計(jì)算的規(guī)則寝受,括號(hào)內(nèi)不會(huì)出現(xiàn)乘法和除法,所以順序計(jì)算即可罕偎。
            int num2 = numStack.pop();
            int num1 = numStack.pop();
            int res = this.calc(num1, num2, operStack.pop());
            numStack.push(res);
            calcBracket(numStack, operStack);
        }
    }

    /**
     * 高優(yōu)先級(jí)運(yùn)算符的運(yùn)算
     */
    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("不能識(shí)別的符號(hào)");
        }
        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("不識(shí)別此符號(hào)");
        }
    }
}

測(cè)試代碼如下

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);
    }

}

以上代碼已通過此測(cè)試代碼很澄。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市颜及,隨后出現(xiàn)的幾起案子甩苛,更是在濱河造成了極大的恐慌,老刑警劉巖俏站,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讯蒲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肄扎,警方通過查閱死者的電腦和手機(jī)墨林,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門赁酝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人旭等,你說我怎么就攤上這事酌呆。” “怎么了搔耕?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵隙袁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我弃榨,道長(zhǎng)菩收,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任鲸睛,我火速辦了婚禮娜饵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腊凶。我一直安慰自己划咐,他們只是感情好拴念,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布钧萍。 她就那樣靜靜地躺著,像睡著了一般政鼠。 火紅的嫁衣襯著肌膚如雪风瘦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天公般,我揣著相機(jī)與錄音万搔,去河邊找鬼。 笑死官帘,一個(gè)胖子當(dāng)著我的面吹牛瞬雹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播刽虹,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼酗捌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了涌哲?” 一聲冷哼從身側(cè)響起胖缤,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阀圾,沒想到半個(gè)月后哪廓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡初烘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年涡真,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了分俯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡综膀,死狀恐怖澳迫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剧劝,我是刑警寧澤橄登,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站讥此,受9級(jí)特大地震影響拢锹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜萄喳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一卒稳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧他巨,春花似錦充坑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至份企,卻和暖如春也榄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背司志。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工甜紫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骂远。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓囚霸,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親激才。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拓型,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容