数据结构---栈及四则运算实现

Posted nijunyang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构---栈及四则运算实现相关的知识,希望对你有一定的参考价值。

假设我们要求输入类似这样一个表达式:9+(3-1)*3+10/2,输出结果。我们知道先括号,再乘除,最后加减,中学时候使用的科学计算器,是允许输入这样的表达式计算结果的,那么计算机怎么知道这个串里面先算括号再算乘除呢?我们先来介绍下栈这种数据结构,再来解决这个问题。

 

前面已经说过数组的连表,现在来说另外一种线性表的数据结构---栈。

举个比较形象的例子,洗盘子的时候,是不是一个一个往上面堆着放,拿的时候也从上面一个一个的拿,最先放的在最下面,最后放的在最上面,拿的时候第一个拿到。这就是典型的栈结构。先进后出First In Last Out(FILO).

 

怎么来实现一个栈结构呢,栈也是一种线性表,前面也有提到两种很基础的线性表结构的数据结构数组和链表。栈其实就是第一个特殊的链表或者数组。可以基于数组或者链表来实现,成为数组栈或者链栈,与之具有数组和链表相关特点。

栈的特殊点在于先进去的元素放在栈低,后进的在栈顶。向栈中插入一个元素叫入栈、进栈、压栈都行,插入的数据会被放在栈顶。从栈中取出一个元素叫出栈、退栈都行,取出之后,原本栈顶的这个元素就会被删掉,让它下面的那个元素成为新的栈顶元素。

数组栈一般栈低是索引开始的元素,压栈就往索引增长方向走;链栈一般栈低是头结点,栈顶是尾结点。

既然都是用数组或链表来实现,为什么还单独拎出来一个数据结构呢。数组和链表暴露了太多了的操作。就会更容易出错。针对性的封装出来的栈这种结构,在某些场景会更加适合。想象一下我们浏览器的的前进后退,是不是就很像两个栈的数据在互相交换操作,一个前进栈,一个后退栈。点后退,把后退栈的栈顶弹出,放进前进栈的栈顶;再点前进,是不是就是压进前进栈顶的后退栈的栈顶元素。就这样互相交替着。

想象一个程序的调用流程是不是也是一个栈结构。最后调用的方法最先执行。

 

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 - 这个时候就把31 相减得到2 => 9 2 3 * 这个时候就把23相乘 得到6 =>

9 6 + => 15 10 2 / =>15 5 + => 20

大致就是这么个流程,这个过程是不是很像栈的操作,遇到数字就入栈,遇到符号就把数字前面两个数字出栈进行计算,然后将结果入栈,直到表达式结束。

 

现在我们只要把中缀表达式转换成后缀表达式就可以进行计算了。看下百度的转换流程

技术图片

 

 

 

简单来说就是用一个栈来存放符号,然后从左到右遍历中缀表达式的数字和字符,若是数字就输出,若是符号则判断和栈顶符号的优先级,如果是括号或优先级低于栈顶元素,则依次出栈并输出,将当前符号进栈,直到最后结束。

 

9+(3-1)*3+10/2

 

先初始化一个栈stack,然后依次遍历我们的中缀表达式,操作逻辑如下:

 

  1. 9 输出 => 9
  2. + 栈空的直接进栈:stack+
  3. ( 未配对的 直接进栈:stack+ (
  4. 3 数字直接输出:9 3
  5. - 前面是( 直接进栈:stack + ( -
  6. 1 直接输出: 9 3 1
  7. )  将前面的符号弹出输出,直到匹配到第一个(为止:9 3 1 -  stack: +
  8. * 优先级高于 + 进栈: stack+ *
  9. 3 输出 9 3 1 - 3
  10. + 优先级低于栈顶的* 将栈顶弹出输出 继续判断之后栈顶是否比+优先级低(同级也弹出,直到有限比栈顶高,或者空栈为止),这里就会连续弹出 * + 然后将当前的 + 入栈:9 3 1 - 3 * +     stack: +
  11. 10 输出:9 3 1 - 3 * + 10
  12. / 优先级高于栈顶 + 直接入栈:stack+ /
  13. 2 直接输出: 9 3 1 - 3 * + 10 2
  14. 最后符号依次出输出:9 3 1 - 3 * + 10 2 / +

 

 

 

从上述逻辑中可以看到,不管是最后的计算,还是中缀表达式转后缀表达式中都用到栈这种数据结构。

 

以上是关于数据结构---栈及四则运算实现的主要内容,如果未能解决你的问题,请参考以下文章

c++实验4 栈及栈的应用+回文+中后缀表达式

TCP/IP协议栈及OSI参考模型详解

C语言链栈及基本操作(包含入栈和出栈)保姆级详解

C语言链栈及基本操作(包含入栈和出栈)保姆级详解

iOS开发--探究iOS线程调用栈及符号化

数据结构(C语言版)之栈及递归