数据结构与算法之二(栈常见案例)

栈是一种常用数据结构,其特性是FILO(first in last out),其基本概念这里不做介绍,相信都学过了。直接食用java中已经封装好Stack<>类。

栈的效率:入栈出栈复杂度为O(1),不需要比较和移动操作。

案例1:单词逆序
比如,输入alphago,要求逆向输出其结果:ogahpla。可以用栈来解决这类问题。

 String word = "alphago";
        Stack<Character> stack = new Stack<Character>();
        for(int i=0;i<word.length();i++){
            stack.push(word.charAt(i));
        }
        /**
         * 这种写法有bug,问题在哪?
         for(int i=0;i<stack.size();i++){
            Character c = stack.pop();
            System.out.print(c);
        }*/
        while(stack.size()!=0){
            System.out.print(stack.pop());
        }

案例2:分隔符匹配
比如有字符串a{b[c(d)c]b}a,如何检测其中的分隔符是一一对应的。用栈来实现也是最简单的。
分析:检测到字符直接不管,检测到左分隔符入栈,检测到右分隔符,从栈中弹出一个符号,如果匹配则继续,如果不匹配或者没有则报错。若有没有匹配的左分隔符也报错。

public static void main(String[] args) {
        // TODO Auto-generated method stub
        String word = "a{b[c(d)c]b}a";
        int result = check(word);
        if(result==-1){
            System.out.println("match success");
        }else{
            System.out.println("match fail: index = "+result+",character is '"+word.charAt(result)+"'");
        }
    }
    /**
     * 匹配错误返回第一个不匹配的位置
     * 匹配正确则返回-1
    */
    private static int check(String word){
        Stack<Character> stack = new Stack<Character>();
        for(int i=0;i<word.length();i++){
            char ch = word.charAt(i);
            switch(ch){
            case '{':
            case '[':
            case '(':
                stack.push(ch);
                break;
            case '}':
            case ']':
            case ')':
                if(stack.isEmpty()){
                    return i;
                }else{
                    char p = stack.pop();
                    if((ch=='}' && p!='{') 
                            || (ch==']' && p!='[')
                            ||(ch==')' && p!='(')){
                        return i;
                    }
                }
                break;
            }
        }
        if(!stack.isEmpty()){
            char c = stack.get(0);
            return word.indexOf(c);
        }
        return -1;
    }

案例3:我觉得这个案例最经典了。如何计算表达式的值。
比如任意一个表达式(1+2*3)/7-1,如何计算出他的正确答案。

分析:这道题光用栈还不够,需要清楚计算的方式
首先,得将中缀表达式变为后缀表达式。
然后,计算后序表达式的值。

1)中缀变后缀
首先借助一个树型图来理解中缀和后缀
在网上找了个图,如下:
这里写图片描述

对这个二叉树做一次中根遍历(即根放在中间,从左到右),可以得到表达式3+2*9-6/4,也就是我们常见的表达式。所以算数表达式都是以中缀表达式出现的。同样,我们可以得到他的后缀表达式,进行一次后跟遍历(即根放在最后,从左到右),可以得到329*+64/-。这就是由中缀变后缀了。同理,我们的表达式画个图,然后也可以写出后缀表达式。

程序实现可以利用栈,情况比较复杂分几种来描述
a. 3+2*9-6/4,*号优先级更高

读取操作栈中输出
33是数字,输出$3
++是计算字符,而栈中此时无元素,入栈$+3
22是数字,输出$+32
*是计算字符,而栈中有元素,弹出+ ,比较和+优先级,优先级>+,先将+入栈,再将入栈$+,*32
99是数字,输出$+,*329
--是计算字符,而栈中有元素,弹出* ,比较-和优先级,-优先级<=,说明前面部分的计算结束了,全部弹出并按顺序输出直到遇到左括号或没了为止,并将-入栈$-329*+
66是数字,输出$-329*+6
//是计算字符,而栈中有元素,弹出- ,比较/和-优先级,/优先级>-,先将-入栈,再将/入栈$-,/329*+6
44是数字,输出$-,/329*+64
end弹出栈中剩余元素,输出$329*+64/-

b. 带括号的表达式(1+2*3)/7-1

读取操作栈中输出
(是左括号,入栈$(
11是数字,输出$(1
++是计算字符,而栈中此时有元素,弹出(,发现不是计算符号,先将(入栈,再将+入栈$(,+1
22是数字,输出$(,+12
*是计算字符,而栈中此时有元素,弹出+,>+,先将+入栈,再将*入栈$(,+,*12
33是数字,输出$(,+,*123
))是右括号,说明此阶段计算结束,依次弹出栈顶元素并输出,直到弹出(丢弃$123*+
//是计算字符,而栈中无元素,入栈$/123*+
77是数字,输出$/123*+7
--是计算字符,而栈中有元素,弹出/,-<=/,上一阶段计算结束,依次弹出并输出直至遇到左括号或结束,将-入栈$-123*+7/
11是数字,输出$-123*+7/1
end弹出栈中剩余元素,输出$123*+7/1-

2)后缀表达式求值
为什么要先变后缀?因为变成后缀的过程中可以处理掉括号,最后利用栈可以求值。比如329*+64/-,我们将所有数字压入栈中,每次碰到符号,则弹出左操作数和右操作数,进行一次计算,然后循环这个过程,就可以算出整个表达式。

329*+64/-

读取操作栈中
33是数字,入栈$3
22是数字,入栈$3,2
99是数字,入栈$3,2,9
**是计算符号,弹出栈顶元素9为右操作数,再次弹出栈顶元素2为左操作数计算2*9=18,将结果压入栈中$3,18
++是计算符号,弹出栈顶元素18为右操作数,再次弹出栈顶元素3为左操作数计算3+18=21,将结果压入栈中$21
66是数字,入栈$21,6
44是数字,入栈$21,6,4
//是计算符号,弹出栈顶元素4为右操作数,再次弹出栈顶元素6为左操作数计算6/4=1.5,将结果压入栈中$21,1.5
--是计算符号,弹出栈顶元素1.5为右操作数,再次弹出栈顶元素21为左操作数计算21-1.5=19.5,将结果压入栈中$19.5
end将栈中结果pop出来即可,结果是19.5$

如果是中缀表达式则无法完成这个过程,因为后缀表达式可以保证计算符号一定有两个操作数在他前面。

假定都是个位数运算,这样可以用String来获取每个字符

/**
     * 获取后缀表达式
     * 如果错误,返回null
     */
    private static String getPostfixExpression(String expression){
        StringBuilder sb = new StringBuilder();
        Stack<Character> stack = new Stack<Character>();
        for(int i=0;i<expression.length();i++){
            char c = expression.charAt(i);
            if(Character.isDigit(c)){  //数字直接输出
                sb.append(c);
            }else if(c=='+' || c=='-'){
                if(stack.isEmpty()){
                    stack.push(c);
                }else{
                    //pop所有并输出,如果遇到左括号则停止,把左括号重新入栈
                    while(!stack.isEmpty()){   //阶段结束标志1
                        char p1 = stack.pop();
                        if(p1!='('){
                            sb.append(p1);
                        }else{   //阶段结束标志2
                            stack.push(p1);
                            break;
                        }
                    }
                    stack.push(c);
                }
            }else if(c=='*'||c=='/'){
                if(stack.isEmpty()){
                    stack.push(c);
                }else{
                    char p = stack.pop();
                    if(p=='*'||p=='/'){
                        sb.append(p);
                        //pop所有并输出,如果遇到左括号则停止,把左括号重新入栈
                        while(!stack.isEmpty()){   //阶段结束标志1
                            char p1 = stack.pop();
                            if(p1!='('){
                                sb.append(p1);
                            }else{   //阶段结束标志2
                                stack.push(p1);
                                break;
                            }
                        }
                        stack.push(c);
                    }else{
                        stack.push(p);
                        stack.push(c);
                    }
                }
            }else if(c=='('){  //左括号直接入栈
                stack.push(c);
            }else if(c==')'){
                //记录是否遇到左括号
                boolean match = false;
                //pop所有并输出,如果遇到左括号则停止,把左括号舍弃
                while(!stack.isEmpty()){   
                    char p = stack.pop();
                    if(p=='('){
                        match = true;
                        break;
                    }else{   
                        sb.append(p);
                    }
                }
                if(!match){
                    return null;
                }
            }else{   //有非法字符
                return null;
            }
        }
        //结束之后,剩下的出栈
        while(!stack.isEmpty()){   
            sb.append(stack.pop());
        }
        return sb.toString();
    }
/**
     * 计算表达式结果
     */
    private static float getResult(String expression){
        Stack<Float> stack = new Stack<Float>();

        for(int i=0;i<expression.length();i++){
            char c = expression.charAt(i);
            if(Character.isDigit(c)){  //数字入栈
                stack.push((float)(c-'0'));
            }else{  //过滤后剩下的肯定是计算符号
                if(stack.size()<2){
                    System.out.println("表达式不正确");
                    return 0;
                }else{
                    float rightNum = stack.pop();
                    float leftNum = stack.pop();
                    float result = 0;
                    switch(c){
                    case '+':
                        result = (leftNum+rightNum);
                        break;
                    case '-':
                        result = (leftNum-rightNum);
                        break;
                    case '*':
                        result = (leftNum*rightNum);
                        break;
                    case '/':
                        result = (leftNum/rightNum);
                        break;
                    }
                    stack.push(result);
                }
            }
        }

        return stack.pop();
    }

上一节:数据结构与算法之一(三种简单排序)
下一节:数据结构与算法之三(栈和队列的java实现)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值