数据结构02-栈、队列和逆波兰表达式

作者: 最爱的火 | 来源:发表于2018-05-05 12:31 被阅读23次

    数据结构02-栈与队列

    一、栈

    栈是限定仅在表尾进行插入和删除操作的线性表。允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表。

    栈的应用:逆波兰表达式。

    1.栈的实现

    栈有两种实现方式:数组和链表。

    使用数组实现比较复杂,需要限定大小,会出现栈溢出的问题。android源码的Stack就是用数组实现的。

    使用链表实现比较简单,不会限定大小,内存占用较高。

    这两种方式的增删改查效率接近。

    2.栈的链式实现

    栈顶添加元素:

    public void add(E e) {
        LinkedTable.Node<E> node = new LinkedTable.Node<E>(e, first);
        first = node;
        size++;
    }
    

    栈顶删除元素:

    public E pop() {
        if (first == null) {
            return null;
        }
        E e = first.item;
        first = first.next;
        size--;
        return e;
    }
    

    二、逆波兰表达式

    1.定义

    逆波兰表达式又叫做后缀表达式。在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之间,这种表示法也称为中缀表示。波兰逻辑学家J.Lukasiewicz于1929年提出了另一种表示表达式的方法,按此方法,每一运算符都置于其运算对象之后,故称为后缀表示。

    2.用途

    逆波兰表达式是一种十分有用的表达式,它将复杂表达式转换为可以依靠简单的操作得到计算结果的表达式。例如(a+b)(c+d)转换为ab+cd+

    3.实现

    将中缀表达式转化为后缀表达式,需要通过栈实现,将字符入栈时需要遵循以下规则:

    1. 如果是数字,直接取出
    2. 如果栈顶为空,直接入栈
    3. 如果是加减乘除,则与栈顶比较,优先级高于栈顶,直接入栈;优先级低于栈顶,先把栈顶出栈,然后与新的栈顶比较;直到优先级高于栈顶,然后入栈;
    4. 如果是'(',直接入栈;
    5. 如果是')',则与栈顶比较,优先级高于栈顶,直接入栈;优先级低于栈顶,先把栈顶出栈,然后与新的栈顶比较;直到栈顶为'(',把'('出栈;
    /**
     * @param expStr 中缀表达式
     * @return 后缀表达式
     */
    public static String getSuffixExpression(String expStr) {
        //添加结束符号')'
        char[] expChars = (expStr + Type.RSP.name).toCharArray();
        Stack<Character> stack = new Stack<Character>();
        StringBuffer stringBuffer = new StringBuffer();
        for (char c : expChars) {
            //如果是运算符
            if (Type.isSymbol(c)) {
                Character top = stack.get();
                if (top == null) {
                    stack.add(c);
                } else if (c == Type.LSP.name) {
                    stack.add(c);
                    //如果优先级低于栈顶
                } else if (Type.compareTo(top, c) >= 0) {
                    while (Type.compareTo(top, c) >= 0) {
                        //添加分隔符
                        stringBuffer.append(star);
                        //取出栈顶元素
                        stringBuffer.append(stack.pop());
                        top = stack.get();
                        if (top == null) {
                            break;
                        }
                        //如果插入的时')',则遇到'('就一起出栈
                        if (top.charValue() == Type.LSP.name && c == Type.RSP.name) {
                            stack.pop();
                            break;
                        }
                    }
                    if (c != Type.RSP.name) {
                        stack.add(c);
                    }
                } else {
                    stack.add(c);
                }
                //添加分隔符
                if (c != Type.LSP.name && c != Type.RSP.name) {
                    stringBuffer.append(star);
                }
            } else {
                stringBuffer.append(c);
            }
        }
        return stringBuffer.toString();
    }
    

    通过工具类判断运算符的优先级:

    /**
     * 运算符号
     */
    public static enum Type {
        ADD('+', 1), MIN('-', 1), MUL('*', 2), DIV('/', 2), LSP('(', 0), RSP(')', 0);
        //运算符名称
        public char name;
        //运算符优先级
        public int index;
    
        Type(char name, int index) {
            this.name = name;
            this.index = index;
        }
    
        /**
         * 获取运算符号的优先级
         *
         * @param c
         * @return
         */
        public static int getID(char c) {
            for (Type type : values()) {
                if (type.name == c) {
                    return type.index;
                }
            }
            return -1;
        }
    
        /**
         * 判断是不是运算符号
         *
         * @param c 字符
         * @return
         */
        public static boolean isSymbol(char c) {
            for (Type type : values()) {
                if (type.name == c) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 比较优先级
         *
         * @param a
         * @param b
         * @return
         */
        public static int compareTo(char a, char b) {
            return getID(a) - getID(b);
        }
    }
    

    4.计算

    将后缀表达式计算出结果时,也需要用到栈:

    /**
     * @param expStr 后缀表达式
     * @return 计算结果
     */
    public static long countSuffixExpression(String expStr) {
        long result = 0;
        if (Tool.isEmpty(expStr)) {
            return result;
        }
        String[] array = expStr.split(Character.toString(star));
        Stack<String> stack = new Stack<String>();
        for (String s : array) {
            char c = s.charAt(0);
            //如果是运算符就计算
            if (Type.isSymbol(c)) {
                String a = stack.pop();
                String b = stack.pop();
                if (!Tool.isEmpty(a) && !Tool.isEmpty(b)) {
                    //先取出来的数放到运算符后面
                    BigDecimal bigA = new BigDecimal(b);
                    BigDecimal bigB = new BigDecimal(a);
                    BigDecimal bigC = null;
                    //做加法运算
                    if (c == Type.ADD.name) {
                        bigC = bigA.add(bigB);
                        //做减法运算
                    } else if (c == Type.MIN.name) {
                        bigC = bigA.subtract(bigB);
                        //做乘法运算
                    } else if (c == Type.MUL.name) {
                        bigC = bigA.multiply(bigB);
                        //做除法运算
                    } else if (c == Type.DIV.name) {
                        bigC = bigA.divide(bigB, 0, BigDecimal.ROUND_UP);
                    }
                    //运算结果入栈
                    stack.add(bigC.toString());
                }
                //如果是字符串,就入栈
            } else {
                stack.add(s);
            }
        }
        result = new BigDecimal(stack.get()).longValue();
        return result;
    }
    

    三、队列

    队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。插入的一端称为队尾,删除的一端称为队头。

    1.实现

    队列也有两种实现:数组和链表。

    数组实现:出队复杂度高0(n);容易假溢出。

    链表实现:实现简单,内存较高。

    注:假溢出是指队列的头部还有空位,尾部已经满了,不再再往尾部插入元素,造成溢出的假象。

    2.常见的队列

    循环队列

    头尾相接的顺序存储结构的队列。解决了假溢出的问题。

    双端队列(Deque)

    一种具有栈和队列性质的数据结构。双端队列中的元素限定从表的两端插入和删除。

    应用:LinkedList/ArrayDeque/ LinkedBlockingDeque(阻塞的双向队列)

    优先级队列

    优先级队列和通常的栈和队列一样,只不过里面的每一个元素都有一个”优先级”,在处理的时候,首先处理优先级最高的。如果两个元素具有相同的优先级,则按照他们插入到队列中的先后顺序处理。

    应用:MessageQueue

    最后

    代码地址:https://gitee.com/yanhuo2008/Common/blob/master/Tool/src/main/java/gsw/tool/datastructure/table/Stack.java

    数据结构与算法专题:https://www.jianshu.com/nb/25128590

    喜欢请点赞,谢谢!

    相关文章

      网友评论

        本文标题:数据结构02-栈、队列和逆波兰表达式

        本文链接:https://www.haomeiwen.com/subject/loisrftx.html