棧的應用之中綴表達式和后綴表達式

版權聲明:本文源自簡書tianma,轉載請務必注明出處: http://www.lxweimin.com/p/a0d4764eba18

中綴表達式: 是一個通用的算術或邏輯公式表示方法, 操作符是以中綴形式處于的中間(例:3 + 4),中綴表達式是人們常用的算術表示方法,但是不易被計算機所解析。
后綴表達式:是一個通用的算術或邏輯公式表示方法, 操作符是后綴形式處于操作數的后面(例:3 4 +),后綴表達式雖然不是人們所習慣的運算表示方法,但是易被計算機解析。

例如:對于中綴表達式 9+(3-1)*2+10/2 , 其后綴表達式是 9 3 1 - 3 * + 10 2 / + , 那么為了方便計算機解析計算,我們需要將中綴表達式轉換成后綴表達式,然后再對后綴表達式進行解析。

1. 中綴表達式轉后綴表達式:

  1. 當讀到一個操作數時,立即將它放到輸出中。讀到的是操作符則需要接著判斷是否該入棧。讀到的是左圓括號則入棧。
  2. 在讀到操作符時,如果棧為空或者棧頂操作符為(,則入棧。如果棧頂操作符不為(,且此操作符優先級小于或等于此時棧頂操作符,則將棧中元素彈出直至 ①遇到左括號 或者 ②棧頂元素為更低優先級 或者 ③棧為空為止,并將當前操作符入棧;否則當前操作符繼續入棧。操作符中,+-優先級低,*/優先級高。
  3. 如果遇到一個右括號,那么就將棧中元素彈出并輸出直至遇到左括號為止。但是這個左括號只被彈出,并不輸出。
  4. 如果讀到輸入的末尾,若棧不為空則將棧元素彈出直到該棧變成空棧,并將彈出的符號寫到輸出中。

"9+(3-1)2+10/2" 轉換過程:*

操作過程 棧中元素 輸出
讀入 9,輸出 9
讀入 +,棧為空,規則2,入棧 + 9
讀入 ( ,左括號,規則1,入棧 + ( 9
讀入 3,輸出 + ( 9 3
讀入 -,棧頂為(,規則2,入棧 + ( - 9 3
讀入 1,輸出 + ( - 9 3 1
讀入 ) ,右括號,規則3,出棧并輸出 + 9 3 1 -
讀入 *,*優先級高于棧頂+,規則,2,入棧 + * 9 3 1 -
讀入 3,輸出 + * 9 3 1 - 3
讀入 +,+優先級低于棧頂*,規則2,棧中元素出棧,當前操作符入棧 + 9 3 1 - 3 * +
讀入 10, 輸出 + 9 3 1 - 3 * + 10
讀入 / , /優先級高于+,入棧 + / 9 3 1 - 3 * + 10
讀入 2, 輸出 + / 9 3 1 - 3 * + 10
讀至末尾,規則4,棧不為空,棧中元素出棧并輸出 9 3 1 - 3 * + 10 / +

2. 后綴表達式計算最終結果:

  1. 從左到右遍歷表達式的每個數字和符號,遇到是數字則進棧,遇到是運算符則將棧頂兩個元素出棧,進行運算并將運算結果進棧;
  2. 遍歷完后綴表達式,此時棧中剩余的數字就是運算結果。

"9 3 1 - 3 * + 10 2 / +" 計算過程:

操作過程 棧中元素
讀入 9,入棧 9
讀入 3,入棧 9 3
讀入 1,入棧 9 3 1
讀入 -,運算并將結果入棧 9 2
讀入 3,入棧 9 2 3
讀入 *,運算并將結果入棧 9 6
讀入 +,運算并將結果入棧 15
讀入 10,入棧 15 10
讀入 2,入棧 15 10 2
讀入 /,運算并將結果入棧 15 5
讀入 +,運算并將結果入棧 20
讀入完畢,棧中元素即為結果 20

簡單中綴表達式計算的java實現:

public class SimpleCalcutor {

    private class Item {

        private String value;
        private Integer number;

        public Item(String value) {
            this.value = value;
            try {
                number = Integer.parseInt(value);
            } catch (Exception ignore) {
            }
        }

        public boolean isNumber() {
            return number != null;
        }

        public int getNumber() {
            if (isNumber())
                return number;
            throw new NumberFormatException();
        }

        public boolean isAdd() {
            return "+".equals(value);
        }

        public boolean isSub() {
            return "-".equals(value);
        }

        public boolean isMul() {
            return "*".equals(value);
        }

        public boolean isDiv() {
            return "/".equals(value);
        }

        public boolean isLeftBracket() {
            return "(".equals(value);
        }

        public boolean isRightBracket() {
            return ")".equals(value);
        }

        public int getPriority() {
            if (isAdd() || isSub())
                return 0;
            if (isMul() || isDiv())
                return 1;
            throw new RuntimeException("This is not +, -, *, /");
        }

        @Override
        public String toString() {
            return value != null ? value.toString() : null;
        }
    }

    /**
     * 計算結果
     * 
     * @param calStr
     * @return
     */
    public int calculate(String calStr) {
        List<Item> infixes = parse(calStr);
        List<Item> postfixes = infix2postfix(infixes);
        return calculateByPostfix(postfixes);
    }

    /**
     * 利用正則表達式將待計算的字符串轉化為List<Item>形式 ,如 10/2 -> [10, /, 2]
     * 
     * @param calStr
     * @return
     */
    private List<Item> parse(String calStr) {
        Pattern pattern = Pattern.compile("\\D|\\d+");
        Matcher m = pattern.matcher(calStr);
        List<Item> items = new ArrayList<Item>();
        while (m.find()) {
            items.add(new Item(m.group(0)));
        }
        return items;
    }

    /**
     * 中綴表達式轉換為后綴表達式
     * <p>
     * 1.當讀到一個操作數時,立即將它放到輸出中。讀到的是操作符則需要接著判斷是否該入棧。讀到的是左圓括號則入棧。<br>
     * 2.如果遇到一個右括號,那么就將棧中元素彈出并輸出直至遇到左括號為止。但是這個左括號只被彈出,并不輸出。<br>
     * 3.在讀到操作符時,如果此操作符優先級小于或等于此時棧頂操作符,則將棧中元素彈出直至(1)遇到左括號或者(2)棧頂元素為更低優先級或者(3)
     * 棧為空為止。操作符中,'+''-'優先級最低,'('')'優先級最高。 <br>
     * 4.如果讀到輸入的末尾,將棧元素彈出直到該棧變成空棧,將符號寫到輸出中。
     * 
     * @return
     */
    private List<Item> infix2postfix(List<Item> infixes) {
        List<Item> postfixes = new ArrayList<Item>();
        Stack<Item> stack = new Stack<Item>();
        for (Item item : infixes) {
            if (item.isNumber()) {
                postfixes.add(item);
            } else if (item.isRightBracket()) {
                // ) 右括號,將棧中元素彈出直至左括號,且左括號和右括號不加入到后綴表達式中
                while (true) {
                    Item tmp = stack.pop();
                    if (tmp.isLeftBracket())
                        break;
                    postfixes.add(tmp);
                }
            } else if (item.isLeftBracket()) {
                // ( 左括號,將左括號入棧
                stack.push(item);
            } else {
                // 當前操作符為 +, -, *, /,
                if (stack.isEmpty()) {
                    // 操作符棧為空,則將當前操作符壓入棧
                    stack.push(item);
                    continue;
                }
                Item top = stack.peek();
                if (top.isLeftBracket()) {
                    // 操作符棧頂為左括號(,則將當前操作符壓入棧
                    stack.push(item);
                    continue;
                }
                if (item.getPriority() <= top.getPriority()) {
                    // 如果此操作符(+,-,*,/)優先級小于或等于此時棧頂操作符
                    // 則將棧中元素彈出直至(1)遇到左括號或者(2)棧頂元素為更低優先級或者(3)棧為空為止
                    // 并將彈出的元素加入后綴表達式中,將當前操作符壓入棧中
                    while (true) {
                        Item tmp = stack.peek();
                        if (tmp.isLeftBracket() || tmp.getPriority() < item.getPriority()) {
                            break;
                        }
                        postfixes.add(tmp);
                        stack.pop();
                        if (stack.isEmpty())
                            break;
                    }
                    stack.push(item);
                } else {
                    // 如果當前操作符(+,-,*,/)優先級大于此時棧頂操作符,則將當前操作符壓入棧
                    stack.push(item);
                }
            }
        }
        // 如果棧中元素不為空,則將棧中元素全部彈出,加入后綴表達式中
        while (!stack.isEmpty()) {
            postfixes.add(stack.pop());
        }
        return postfixes;
    }

    /**
     * 通過后綴表達式計算數值
     * <p>
     * 1. 從左到右遍歷表達式的每個數字和符號,遇到是數字則進棧,遇到是運算符則將棧頂兩個元素出棧,進行運算并將運算結果進棧<br>
     * 2. 遍歷完后綴表達式,此時棧中剩余的數字就是運算結果
     * 
     * @param postfixes
     * @return
     */
    private int calculateByPostfix(List<Item> postfixes) {
        Stack<Integer> stack = new Stack<Integer>();
        for (Item item : postfixes) {
            if (item.isNumber()) {
                stack.push(item.getNumber());
            } else {
                // 運算符
                int num1 = stack.pop();
                int num2 = stack.pop();
                int result;
                if (item.isAdd()) {
                    result = num2 + num1;
                } else if (item.isSub()) {
                    result = num2 - num1;
                } else if (item.isMul()) {
                    result = num2 * num1;
                } else if (item.isDiv()) {
                    result = num2 / num1;
                } else {
                    throw new IllegalArgumentException("Operator invalid : " + item.value);
                }
                stack.push(result);
            }
        }
        return stack.pop();
    }

    public static void main(String[] args) {
        SimpleCalcutor calcutor = new SimpleCalcutor();
        String calStr = "9+(3-1)*3+10/2";
        int result = calcutor.calculate(calStr);
        System.out.println(result);
    }

}

源碼github地址:
SimpleCalculator

參考鏈接:
利用棧將中綴表達式轉換成后綴表達式

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容

  • 在某些情況下,我們需要對輸入字符串表達式進行計算,例如一個字符串為:“1 + 2 * 3”,我們需要計算出它的結果...
    酸菜Amour閱讀 1,550評論 0 6
  • 棧的規則 先進后出。如:依次入棧順序為:A,B,C,D;怎出棧順序為:D,C,B,A . 二叉樹和表達式 表達式的...
    zhangivon閱讀 852評論 0 0
  • 基礎知識 基本概念 常見數據結構 棧和隊列 棧Stack 隊列Queue 樹和堆 樹的定義 樹(tree)是包含n...
    passwd_閱讀 1,476評論 0 2
  • 棧模型## 棧(Stack)是限制插入和刪除只能在一個位子上進行的表,該位子是表的末端,叫做棧的頂(top)。對棧...
    kylinxiang閱讀 1,685評論 0 1
  • 第一步,抬頭。第二步,閉眼。 這樣,眼淚就都流進心里了。 我想起來了。我從來沒有不喜歡你, 那些讓你傷心的難聽話,...
    到底要多久閱讀 181評論 0 0