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

数据结构02-栈与队列

一、栈

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

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

1.栈的实现

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

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

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

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

2.栈的链式实现

栈顶添加元素:

public void add(E e) {
    LinkedTable.Node node = new LinkedTable.Node(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 stack = new Stack();
    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 stack = new Stack();
    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-栈、队列和逆波兰表达式)