• 栈和队列的应用


    1.栈常见应用

    1.1 括号匹配

    问题描述:假设表达有两种符号:圆的和方的,嵌套的顺序任意,判断嵌套是否正确,如 ([]()) 或 [[()]]均为正确,而 [(]) 或 (()] 均为不正确。

    算法描述:

       (1)初始化一个空栈,顺序读入括号;

       (2)若是左括号直接进栈;

       (3)若是右括号,先出栈一个元素比较是否与其匹配,如匹配则继续比较下一个括号,若不匹配则返回匹配失败;

       (4)最后若栈不为空,说明还有没匹配的括号,匹配失败,否则匹配成功。

    /**
     * 检查括号是否匹配
     * @param match 待匹配字符串
     * @return true 匹配成功,false 匹配失败
     */
    public static boolean isMatching(String match){
        char[] braces = match.trim().toCharArray();
        for(char ch : braces){
            if(ch == '[' || ch == '('){
                stack.push(ch);
            } else if(ch == ']'){
                char t = stack.pop();
                if('[' == t){
                    continue;
                } else {
                    return false;
                }
            } else if(ch == ')'){
                char c = stack.pop();
                if('(' == c){
                    continue;
                } else {
                    return false;
                }
            }
        }
        if(stack.size() > 0){
            return false;
        } else {
            return true;
        }
    }

    1.2 表达式求值

          算术表达式由括号、运算符和操作数组成,表达式求值的关键在于如何解析字符串,并按照正确的顺序完成运算。表达式有中缀表达式,不仅要考虑运算符优先级,还要考虑括号,后缀表达式已经考虑了优先级,只有操作数和运算符,在此提供两种方案解决表达式求值。

          首先定义运算符优先级,运算符只存在两个位置,栈内和栈外,优先级也分为栈内优先级(in stack priority)和栈外优先级(in comming priority),栈的特性是后进先出,栈外的运算符优先级大,则入栈它迫切先计算,对于相同级别的运算符,栈内的优先级大于栈外的优先级,则出栈。据此设计运算符优先级表:(用isp,icp分别表示栈内和栈外优先级,#号表示字符串结束)

    表1 运算符优先级表

    操作符

    #

    (

    +,-

    *,/

    )

    isp

    0

    1

    3

    5

    6

    icp

    0

    6

    2

    4

    1

          在Java中使用两个HashMap存储栈内和栈外运算符优先级,代码如下:

    static ArrayStack<String> ops = new ArrayStack<String>(10); // 运算符栈
    static ArrayStack<Double> vals = new ArrayStack<Double>(20); // 操作数栈
    
    static String opstr = "(+-*/)"; // 涉及到的运算符
    
    static Map<String, Integer> isp = new HashMap<String, Integer>(); // 栈内优先级     
    static Map<String, Integer> icp = new HashMap<String, Integer>(); // 栈外优先级
    static{
        isp.put("#", 0);isp.put("(", 1); isp.put("+", 3); isp.put("-", 3); isp.put("*", 5); isp.put("/", 5); isp.put(")", 6);
        icp.put("#", 0);icp.put("(", 6); icp.put("+", 2); icp.put("-", 2); icp.put("*", 4); icp.put("/", 4); icp.put(")", 1);
    }

    1.2.1 直接使用中缀表达式求值

       算法描述:

       (1)初始化两个栈,操作数栈和运算符栈,顺序读取字符串

       (2)如果是操作数直接入操作数栈;

       (3)如果是运算符,如果当前运算符优先级大于运算符栈顶优先级,则直接入运算符栈,如果小于则循环弹出一个操作符,两个操作数计算,并将结果压入操作数 栈,直到找到运算符优先级比它小的,最后如果当前运算符是右括号,则弹出一个运算符(弹出的是左括号),否则入运算符栈;

       (4)如果字符为#说明已经到表达式末尾,则循环弹出操作符栈中的运算符及操作数计算,直到运算符栈为空,最后操作数栈中的元素即为结果。

    /**
     * 中缀表达式计算
     */
    public static double infix(String expr){
        ops.push("#");
        char[] chs = expr.trim().toCharArray();
        for(char ch : chs) {
            String op = String.valueOf(ch);
            // 如果是# 表示表达式结尾,把栈中的运算符全部弹出
            if("#".equals(op)){
                while(ops.size() > 0 && !"#".equals(ops.peek())){
                    vals.push(calculate(vals.pop(), vals.pop(), ops.pop().charAt(0)));
                }
            } else if(!opstr.contains(op)){// 如果是操作数,直接输出
                vals.push(Double.valueOf(op));
            } else { // 运算符
                // 当前运算符大于栈顶运算符优先级,则直接入操作符栈
                if(icp.get(op) > isp.get(ops.peek())){
                    ops.push(op);
                } else {
                    // 如果小于,循环出栈找到比它优先级小的,弹出一个操作符,两个操作数,计算结果置入操作数栈
                    // 如果是 右括号) 即是匹配栈内的左括号),最后弹出),(不入栈
                    // 如果不是右括号,最后把这个运算符压入运算符栈
                    while(icp.get(op) < isp.get(ops.peek())){
                        vals.push(calculate(vals.pop(), vals.pop(), ops.pop().charAt(0))); 
                    }
                    
                    if(!")".equals(op)){
                        ops.push(op);
                    } else {
                        ops.pop(); //弹出  ( 左括号
                    }
                }
            }
        }
        return vals.pop();
    }

    1.2.2 使用后缀表达式求值

          后缀表达式已经包含了操作数以及运算符优先级,计算比较方便,遇操作数直接入栈,遇运算符弹出两个操作数计算后再入栈,如此循环,最后操作数栈内即为结果。关键在于如何把中缀表达式转为后缀表达式。

       中缀表达式转后缀表达式,算法描述:

       (1)初始化一个运算符栈,顺序读取表达式字符串

       (2)如果字符是操作数直接输出

       (3)如果字符是运算符,如果当前运算符优先级大于运算符栈顶优先级,则直接入运算符栈,如果小于则循环弹出一个操作符,直到找到运算符优先级比它小的,最 后如果当前运算符是右括号,则弹出一个运算符(弹出的是左括号),否则入运算符栈;

       (4)若字符为# 表示已经到末尾,循环弹出运算符栈中的元素,直到栈空,最后的输出即为中缀表达式。

    /**
     * 中缀表达式转后缀
     * @param expr
     */
    public static String infix2suffix(String expr){
        ops.push("#");
        StringBuffer sb = new StringBuffer();
        char[] chs = expr.trim().toCharArray();
        for(char ch : chs){
            String op = String.valueOf(ch);
            
            // 如果是# 表示表达式结尾,把栈中的运算符全部弹出
            if("#".equals(op)){
                while(ops.size() > 0 && !"#".equals(ops.peek())){
                    sb.append(ops.pop());
                }
            } else if(!opstr.contains(op)){// 如果是操作数,直接输出
                sb.append(op);
            } else { // 运算符
                // 如果当前运算符优先级大于栈顶运算符优先级,则入栈
                if(icp.get(op) > isp.get(ops.peek())) {
                    ops.push(op);
                    continue;
                } 
                // 如果小于,弹出一个操作符,循环出栈直到比它优先级小
                // 如果是 右括号) 即是匹配栈内的左括号),最后弹出),(不入栈
                // 如果不是右括号,最后把这个运算符压入运算符栈
                while(icp.get(op) < isp.get(ops.peek())){
                    sb.append(ops.pop());
                }
                
                if(!")".equals(op)){
                    ops.push(op);
                } else {
                    ops.pop(); //弹出  ( 左括号
                }
            }
        }
        return sb.toString();
    }

    1.2.3 递归

        所谓递归,就是函数调用了自己,以斐波那契数列为例,其定义为:

    这是一个典型的递归例子,Java程序实现如下:

    public static int Fib(int n){
        if(n == 0){
            return 0;
        } else if(n == 1){
            return 1;
        } else {
            return Fib(n-1) + Fib(n-2);
        }
    }

       递归的效率不高,由于其中重复包含很多重复的计算。每个线程执行时,都有各自的栈,我们在debug时可以看到,在递归调用时,递归期间的数据都是保存在这个栈中,系统帮我们维护这个栈。

       通常情况下将递归程序转非递归程序,使用自定义栈模拟递归过程,几乎适用于所有递归,站在系统的角度,栈能解决所有问题。但是具体问题,也有其他方法转为非递归,如斐波那契数列可使用直接迭代计算出结果。

    /**
     * 非递归实现,不借助栈,直接迭代
     * @return
     */
    public static int NotRecur(int n){
        if(n == 0){
            return 0;
        } else if(n == 1){
            return 1;
        } else {
            int n0 = 0,
                n1 = 1; // n 为0,1时的初值
            while(n >= 2){
                int tmp = n1 + n0;
                n0 = n1;
                n1 = tmp;
                n--;
            }
            return n1;
        }
    }

    后续有些算法,如二叉树的遍历,会分为递归实现和非递归实现。

    2.队列常见应用

    2.1 层次遍历

    在信息处理时,有一类问题需要逐层或逐行遍历,比如二叉树的层次遍历,图的广度优先搜索等。

    2.2 缓冲区

    缓冲区,是为了匹配速度,主要解决主机和外设速度不匹配,解决多用户引起资源竞争问题。

     

    作 者:创心coder
    QQ群:361982568
    订阅号:cxcoder
    文章不足之处,还请谅解!本文会不断完善更新,转载请保留出处。如果帮到了您,烦请点赞,以资鼓励。
  • 相关阅读:
    黑马程序员——JAVA基础之System,Runtime,Date,Calendar,Math
    黑马程序员——JAVA基础之JDK1.5新特性高级for循环和可变参数
    黑马程序员——JAVA基础之Collections和Arrays,数组集合的转换
    黑马程序员——JAVA基础之Map集合
    黑马程序员——仅当源级别为 1.5 时已参数化的类型才可用的解决办法
    黑马程序员——JAVA基础之泛型和通配符
    黑马程序员——JAVA基础之Vector集合
    黑马程序员——JAVA基础之set集合
    黑马程序员——JAVA基础之List集合
    Bringing up interface eth0: Error: No suitable device found: no device found for connection 'System eth0'.
  • 原文地址:https://www.cnblogs.com/cwane/p/5510226.html
Copyright © 2020-2023  润新知