假设咱们要求输入相似这样一个表达式:9+(3-1)*3+10/2,输出结果。咱们知道先括号,再乘除,最后加减,中学时候使用的科学计算器,是容许输入这样的表达式计算结果的,那么计算机怎么知道这个串里面先算括号再算乘除呢?咱们先来介绍下栈这种数据结构,再来解决这个问题。数组
前面已经说过数组的连表,如今来讲另一种线性表的数据结构---栈。浏览器
举个比较形象的例子,洗盘子的时候,是否是一个一个往上面堆着放,拿的时候也从上面一个一个的拿,最早放的在最下面,最后放的在最上面,拿的时候第一个拿到。这就是典型的栈结构。先进后出First In Last Out(FILO).数据结构
怎么来实现一个栈结构呢,栈也是一种线性表,前面也有提到两种很基础的线性表结构的数据结构数组和链表。栈其实就是第一个特殊的链表或者数组。能够基于数组或者链表来实现,成为数组栈或者链栈,与之具备数组和链表相关特色。this
栈的特殊点在于先进去的元素放在栈低,后进的在栈顶。向栈中插入一个元素叫入栈、进栈、压栈都行,插入的数据会被放在栈顶。从栈中取出一个元素叫出栈、退栈都行,取出以后,本来栈顶的这个元素就会被删掉,让它下面的那个元素成为新的栈顶元素。google
数组栈通常栈低是索引开始的元素,压栈就往索引增加方向走;链栈通常栈低是头结点,栈顶是尾结点。spa
既然都是用数组或链表来实现,为何还单独拎出来一个数据结构呢。数组和链表暴露了太多了的操做。就会更容易出错。针对性的封装出来的栈这种结构,在某些场景会更加适合。想象一下咱们浏览器的的前进后退,是否是就很像两个栈的数据在互相交换操做,一个前进栈,一个后退栈。点后退,把后退栈的栈顶弹出,放进前进栈的栈顶;再点前进,是否是就是压进前进栈顶的后退栈的栈顶元素。就这样互相交替着。code
想象一个程序的调用流程是否是也是一个栈结构。最后调用的方法最早执行。blog
Java里面的Stack也是基于数组实现的,它继承了Vector。咱们用数组实现一个简单栈的基本操做:继承
package com.nijunyang.algorithm.stack; /** * Description: * Created by nijunyang on 2020/4/1 23:48 */ public class MyStack<E> { private static final int DEFAULT_SIZE = 10; private Object[] elements; private int size; public MyStack() { this(DEFAULT_SIZE); } public MyStack(int capacity) { this.elements = new Object[capacity]; } /** * 入栈 * @param e */ public void push(E e) { //弹性伸缩,扩容/收缩释放内存空间 if (size >= elements.length) { resize(size * 2); } else if (size > 0 && size < elements.length / 2) { resize(elements.length / 2); } elements[size++] = e; } /** * 出栈 */ public E pop() { if (isEmpty()) { return null; } E e = (E) elements[--size]; //size是5,那么最后一个元素就是4也就是--size elements[size] = null; //如今size已是4了,弹出就是4这个元素的位置置为空 return e; } public boolean isEmpty() { return size == 0; } public int size() { return size; } /** * 扩容/收缩 */ private void resize(int newCapacity) { Object[] temp = new Object[newCapacity]; for(int i = 0 ; i < size; i ++){ temp[i] = elements[i]; } elements = temp; } public static void main(String[] args){ MyStack<Integer> myStack = new MyStack(5); myStack.push(1); myStack.push(2); myStack.push(3); myStack.push(4); myStack.push(5); myStack.push(6); System.out.println(myStack.size); Integer e = myStack.pop(); System.out.println(e); e = myStack.pop(); System.out.println(e); e = myStack.pop(); System.out.println(e); e = myStack.pop(); System.out.println(e); e = myStack.pop(); System.out.println(e); e = myStack.pop(); System.out.println(e); e = myStack.pop(); System.out.println(e); } }
如今用咱们看看怎么用栈来解决9+(3-1)*3+10/2这个计算问题索引
首先咱们要怎么来处理括号和运算符号的优先级呢
这里先说一下中缀表达式和后缀表达式,像这个表达式9+(3-1)*3+10/2就是中缀表达式,若是咱们转换成9 3 1 - 3 * + 10 2 / + 这个就是后缀表达式,后缀表达式也叫逆波兰,能够能够自行百度或者google,后缀表达式就是操做符号在两个操做数的后面,而中缀表达式就是操做符号在两个操做数的中间。
看下后缀表达式是怎么操做的,就是遇到操做符号就把前面两个数进行符号运算:
9 3 1 - 3 * + 10 2 / + 这个表达式的操做以下:
9 3 1 - 这个时候就把3和1 相减获得2 => 9 2 3 * 这个时候就把2和3相乘 获得6 =>
9 6 + => 15 10 2 / =>15 5 + => 20
大体就是这么个流程,这个过程是否是很像栈的操做,遇到数字就入栈,遇到符号就把数字前面两个数字出栈进行计算,而后将结果入栈,直到表达式结束。
如今咱们只要把中缀表达式转换成后缀表达式就能够进行计算了。看下百度的转换流程
简单来讲就是用一个栈来存放符号,而后从左到右遍历中缀表达式的数字和字符,如果数字就输出,如果符号则判断和栈顶符号的优先级,若是是括号或优先级低于栈顶元素,则依次出栈并输出,将当前符号进栈,直到最后结束。
9+(3-1)*3+10/2
先初始化一个栈stack,而后依次遍历咱们的中缀表达式,操做逻辑以下:
从上述逻辑中能够看到,无论是最后的计算,仍是中缀表达式转后缀表达式中都用到栈这种数据结构。