栈的应用之中缀表达式和后缀表达式

版权声明:本文源自简书tianma,转载请务必注明出处: http://www.jianshu.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阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 在某些情况下,我们需要对输入字符串表达式进行计算,例如一个字符串为:“1 + 2 * 3”,我们需要计算出它的结果...
    酸菜Amour阅读 1,435评论 0 6
  • 栈的规则 先进后出。如:依次入栈顺序为:A,B,C,D;怎出栈顺序为:D,C,B,A . 二叉树和表达式 表达式的...
    zhangivon阅读 752评论 0 0
  • 基础知识 基本概念 常见数据结构 栈和队列 栈Stack 队列Queue 树和堆 树的定义 树(tree)是包含n...
    passwd_阅读 1,411评论 0 2
  • 栈模型## 栈(Stack)是限制插入和删除只能在一个位子上进行的表,该位子是表的末端,叫做栈的顶(top)。对栈...
    kylinxiang阅读 1,602评论 0 1
  • 第一步,抬头。第二步,闭眼。 这样,眼泪就都流进心里了。 我想起来了。我从来没有不喜欢你, 那些让你伤心的难听话,...
    到底要多久阅读 164评论 0 0