2.4 栈

       栈的特征是先进后出,英文缩写为FILO(first in, last out),也就是说所有的添加删除操作,只在尾部进行。根据前面我们的学习,链表、数组列表、循环队列都适合当作栈使用。栈在计算机世界最广泛的应用就是程序函数的调用,因为函数内部还会调用调用,所以是一个树状结构,而函数调用必须先执行最内层的子函数,所以这相当于一个深度优先搜索(DFS)算法,而支撑深度优先搜索算法的核心数据结构就是栈。

       这里不讨论深度优先搜索。

       先以一个常见的需求表达式解析开始。这里为了简化需求,不考虑代数运算表达式,只考虑数值运算表达式。如下列例子:

       1+2*(3+4)-7

       这个我用所毕生所学数学知识,计算出来了,结果是8。但是怎么用程序计算呢?因为涉及字符串解析,所以继续简化需求,我把这一步省略了,直接人工拆成了数组。比如java代码如此表示

       final Object[] objects = {1, '+', 2, '*', '(', 3, '+', 4, ')', '-', 7};

       写代码,什么情况下第一步都是仔细分析需求,先观察输入的序列:

       1 + 2 * ( 3 + 4 ) - 7

       我看了十遍,得到一个结论,这个序列包含两种不同类型的数据,一种是数字,一种是符号。如果你们一遍就看出来了,那么智商比我高。

       我又看了十遍,发现了这个表达式其实是一个二叉树状结构

       这棵树很像计算机的函数调用,可以这么说深刻地理解了这棵树,也就理解了计算机的运算步骤了。

       从这棵树可以看出一个规律,所有的叶子都是数字,所有的非叶子都是运算符。再回头看看我们的输入顺序。

       1 + 2 * ( 3 + 4 ) -7

       又发现了一个规律,最先放入的1,并不能马上运算,而是要等待乘法的运算结果。而减号出现后,就必须计算1和右边这棵子树的和了。

       规律一:同优先级(其实低优先级也一样)运算符出现后,必须马上运算(加法满足结合律,但是减法不满足结合律,所以必须马上运算);高优先级运算符出现不能运算。

       再考虑括号这个东西。括号是用来强制改变优先级的。但是括号也有不同于普通运算符的特征,括号不是一个二元运算符。因为一对括号包着一个子表达式,一个子表达式相当于一个子树。按这个思维,括号算是一个“半元运算符”,左右括号两个运算符才作用于一个数据,平均下来,一个运算符才对应半个数据。

       在结合上面的例子分析左右括号的左右。因为有低运算符出现必须马上终止运算的规律,但是左括号的出现,卡在了乘法和加法之间,使得加法树下降了一层,也就是搁置了加法的运算。右括号的出现,标志这一个子表达式的结束,所以必须马上进行运算。

       于是我们掌握了一个新的规律

       规律二:左括号阻止按优先级的“必须”运算规律,右括号阻止优先级的“搁置”运算规律。

       需求分析完了,现在就是选择数据结构了。因为存在搁置运算,先放入的比如图中的数字1,却要最后运算,我们自然想到了栈。又因为数据分两种类型,我们自然想到了用两个栈来做,一个放数据,一个放运算符。

       用栈解开表达式很简单。就是两个栈,一个叫操作数栈,一个叫操作符栈。

       遇到操作符时,如果优先级低于或等于栈顶操作符优先级,则从操作数栈弹出两个元素进行计算,并压入操作数栈,继续与栈顶操作符的比较优先级,直到遇到左括号或更低优先级运算符为止。

       如果遇到的是右括号,需要循环取出栈顶元素进行计算,直到遇到左括号为止。

       这个继续比较就是循环比较的意思。

       等于是遇到加法呢,要把之前存储的所有乘法和除法计算完毕。

       上述算法,就是著名的表达式解析算法:Dijkstra's two-stack algorithm。用我的小学水平英语翻译下就是迪科斯彻双栈算法。  

       他的发明者就是计算机算法大神,1972年图灵奖得主。

       最后贴上Java代码实现:

public class Expression {

    private static int getPriority(Character c) {
        switch (c) {
            case '+':
            case '-':
                return 1;
            case '*':
            case '/':
                return 2;
            default:
                throw new UnsupportedOperationException("不支持的运算符" + c);
        }
    }

    public static Integer evaluate(Object[] expression) {
        Deque<Integer> operand = new ArrayDeque<>();
        Deque<Character> operator = new ArrayDeque<>();
        for (Object s : expression) {
            if (s instanceof Integer) {
                operand.addLast((Integer) s);
            } else {
                Character e = (Character) s;
                if ('(' == e) {
                    operator.addLast(e);
                } else if (')' == e) {
                    //弹出运算符,进行运算,直到弹出(
                    for (Character op = operator.removeLast(); op != '('; op = operator.removeLast()) {
                        evaluate(operand, op);
                    }
                } else {
                    // 看看栈顶的优先级
                    for (Character peek = operator.peekLast();
                         peek != null && peek != '(' && getPriority(e) <= getPriority(peek);
                         peek = operator.peekLast()) {
                        // 弹出栈进行运算
                        peek = operator.removeLast();
                        evaluate(operand, peek);
                    }
                    // 运算符压入栈
                    operator.addLast(e);
                }
            }
        }

        while (!operator.isEmpty()) {
            Character s = operator.removeLast();
            evaluate(operand, s);
        }

        return operand.pop();
    }


    private static void evaluate(Deque<Integer> operand, Character s) {
        Integer b = operand.removeLast();
        Integer a = operand.removeLast();
        Integer result = evaluate(a, b, s);
        operand.addLast(result);
    }


    private static Integer evaluate(Integer a, Integer b, Character s) {
        switch (s) {
            case '+':
                return a + b;
            case '-':
                return a - b;
            case '*':
                return a * b;
            case '/':
                return a / b;
            default:
                throw new UnsupportedOperationException("不支持的运算符" + s);
        }
    }

}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

醒过来摸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值