数据结构与算法(五)栈

1.什么是栈

栈是一种基于先进后出(FILO)的数据结构(队列是先进先出),是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
我们称数据进入到栈的动作为压栈,数据从栈中出去的动作为弹栈

在这里插入图片描述

2.栈的实现(链表)

2.1栈的API设计

类名 Stack<T>
构造方法 Stack():创建Stack对象
成员方法 1.public boolean isEmpty():判断栈是否为空,是返回true,不是返回false
2.public int size():获取栈中的元素个数
3.public T pop():弹出栈顶元素
4.public void push(T t):向栈中压入元素t
成员变量 1.private Node head:记录头节点
2.privateint length:当前栈的元素个数
成员内部类 private class Node:节点类

2.2成员方法的实现思路

  1. isEmpty():根据Stack类中的成员变量length判断它是否等于0就可以,为0就是空。
  2. size():直接返回Stack类中的成员变量length就可以了
  3. push():因为栈是先进后出的,所以我们每次在压栈时都是让新元素插入到头节点的下一个位置,在每次弹栈时,只需要获取头节点的下一个位置的元素即可,这样就满足了数据的先进后出,实现了压栈。
  4. pop():将头节点的下一个元素返回,然后删除该元素即可。

3.3实现代码

package com.lzf.linkedlist2;

import java.util.Iterator;

public class Stack<T> implements Iterable<T>{
    //记录头节点
    private Node head;
    //记录栈中元素个数
    private int length;

    //构造器
    public Stack() {
        //初始化成员变量
        head = new Node(null,null);
        length = 0;
    }

    //1.判断栈是否为空,是返回true,不是返回false<br>
    public boolean isEmpty(){
        return length==0;
    }
    //2.获取栈中的元素个数<br>
    public int size(){
        return length;
    }
    //3.弹出栈顶元素<br>
    public T pop(){
        //1.找到头节点指向的第一个节点
        Node firstNode = head.next;
        //2.让首节点指向原来第一个节点的下一个节点
        if(firstNode==null){
            return null;
        }
        head.next = firstNode.next;
        //3.元素个数减1
        length--;
        return firstNode.data;
    }
    //4.向栈中压入元素t
    public void push(T t){
        //1.找到头节点指向的第一个节点
        Node firstNode = head.next;
        //2.创建新节点
        Node newNode = new Node(t,null);
        //3.让头节点指向新系欸但
        head.next = newNode;
        //4.让新节点指向原来的第一个节点
        newNode.next = firstNode;
        //5.元素个数加1
        length++;
    }

    //节点类(单向链表的节点)
    private class Node{
        //存储数据
        public T data;
        //指向下一个节点
        public Node next;
        public Node(T data, Node next) {
            this.data = data;
            this.next = next;
        }
    }

    //实现Iterable接口重写的方法
    @Override
    public Iterator<T> iterator() {
        return new SItertor();
    }

    //重写的iterator()方法需要返回的实现类
    private class  SItertor implements  Iterator{
        private Node node;

        public SItertor() {
            this.node = head;
        }

        @Override
        public boolean hasNext() {
            return node.next!=null;
        }

        @Override
        public Object next() {
            node = node.next;
            return node.data;
        }
    }


}

3.测试及效果

3.1测试代码

package com.lzf.linkedlist2;

public class TestStack {
    public static void main(String[] args) {
        //创建栈
        Stack<String> stack = new Stack<>();

        //测试栈是否为空
        System.out.println("栈是否为空:"+stack.isEmpty());
        //测试压栈
        stack.push("孙悟空");
        stack.push("猪八戒");
        stack.push("沙僧");
        stack.push("唐僧");
        System.out.println("遍历栈结果:");
        for(String data : stack){
            System.out.println(data);
        }
        //栈压入数据后测试栈是否为空
        System.out.println("栈压入数据后是否为空:"+stack.isEmpty());

        //测试弹栈
        String result = stack.pop();
        System.out.println("弹出栈的元素是:"+result);
        System.out.println("栈剩余元素个数:"+stack.size());

    }
}

3.2效果截图

在这里插入图片描述

4逆波兰表达式

要搞清楚逆波兰表达式,我们要先搞清楚中缀表达式

4.1中缀表达式

 中缀表达式就是我们生活中使用的表达式,例如:1+3*2,2-(1+3)等等,中缀表达式的特点是:二元运算符总是置于两个操作数中间。
 中缀表达式是人们最喜欢的表达式方式,因为简单,易懂。但是对于计算机来说就不是这样了,因为中缀表达式的运算顺序不具有规律性。不同的运算符具有不同的优先级,如果计算机执行中缀表达式,需要解析表达式语义,做大量的优先级相关操作。

4.2逆波兰表达式(后缀表达式)

 逆波兰表示法是波兰逻辑学家J・卢卡西维兹(J・ Lukasewicz)于1929年首先提出的一种表达式的表示方法,后缀表达式的特点:运算符总是放在跟它相关的操作数之后。

中缀表达式 后缀表达式
a+b ab+
a+(b-c) abc-+
a+(b-c)*d abc-d*+
a*(b-c)+d abc-*d+

或许看图还是不是很清楚这个是怎么计算的。那我就再来解释一下:
其实很简单,操作符的前面两个数就是这个操作符的两个操作数,我们用上面表格的4个表达式举例:

  1. 第一个后缀表达式只有一个操作符 " + ",它前面的两个数(a,b)就是操作数即a+b
  2. 第二个后缀表达式有两个操作符,第一个操作符 " - ",它前面的两个数(b,c)就是操作数即b-c,然后第二个操作符 " + ",它前面的两个数(a,(b-c)这个b-c已经运算过,它现在是一个数)就是操作数即a+(b-c)
  3. 第三个后缀表达式有三个操作符,覅一个操作符 " - ",它前面的两个数(b,c)就是操作数即b-c,接着第二个操作符 " * ",它前面的两个数((b-c),d)就是操作数即(b-c)*d,最后一个操作数 " + ",它前面的两个数(a,(b-c)*d)就是操作数即a+(b-c)*d
  4. 最后一个我们用具体数字来计算,假设a,b,c,d的值各为1,2,3,4.
    第一个操作符 " - " 先算b-c= 2-3 = -1
    第二个操作符 " * "再算a * (b-c)= 1*-1 = -1
    最后的操作符 " + "最后算a*(b-c) + d=-1 + 4 = 3

4.3小案例

需求: 给定一个只包含加减乘除四种运算的逆波兰表达式的数组表示方式,求出该逆波兰表达式的结果。

//中缀表达式 3*(17-15)+18/6 的逆波兰表达式如下
String[] notation = {“3”, “17”, “15”, “-”, “*”, “18”, “6”, “/”, “+”};

实现思路

  1. 创建一个栈,用来存储操作数
  2. 遍历逆波兰表达式,得到每一个元素
  3. 判断当前元素是运算符还是操作数
  4. 如果是运算符就从栈中弹出两个操作数,完成运算,运算完的结果在压入栈中
  5. 如果是操作数就把该操作数压入栈中
  6. 得到栈中的最后一个元素,就是逆波兰表达式的结果

4.4实现代码

package com.lzf.linkedlist2;

//逆波兰表达式(后缀表达式)
public class ReversePolishNotationTest {
    public static void main(String[] args) {
        //中缀表达式 3*(17-15)+18/6 的逆波兰表达式如下
        String[] notation = {"3", "17", "15", "-", "*", "18", "6", "/", "+"};
        int result = caculate(notation);
        System.out.println("结果为:" + result);
    }

    private static int caculate(String[] notation) {
        //1.创建一个栈,用来存储操作数
        Stack<Integer> oprands = new Stack<>();
        //2.遍历逆波兰表达式,得到每一个元素
        for (int i = 0; i < notation.length; i++) {
            String curr = notation[i];
            //3.判断当前元素是运算符还是操作数
            Integer o1;
            Integer o2;
            int result;
            switch (curr) {
                //4.运算符:从栈中弹出两个操作数,完成运算,运算完的结果在压入栈中
                case "+":
                    o1 = oprands.pop();
                    o2 = oprands.pop();
                    result = o2 + o1;
                    oprands.push(result);
                    break;
                case "-":
                    o1 = oprands.pop();
                    o2 = oprands.pop();
                    result = o2 - o1;
                    oprands.push(result);
                    break;
                case "*":
                    o1 = oprands.pop();
                    o2 = oprands.pop();
                    result = o2 * o1;
                    oprands.push(result);
                    break;
                case "/":
                    o1 = oprands.pop();
                    o2 = oprands.pop();
                    result = o2 / o1;
                    oprands.push(result);
                    break;
                default:
                    //5.操作数:把该操作数压入栈中
                    oprands.push(Integer.parseInt(curr));
                    break;
            }
        }
        //6.得到栈中的最后一个元素,就是逆波兰表达式的结果
        int result = oprands.pop();
        return result;
    }
}

4.5实现结果

结果为9就是正确的结果
在这里插入图片描述

4.6避免踩坑

在这里插入图片描述
我们在第四步对栈中弹出的两个数进行运算时,要注意这个两个数(o1,o2)的顺序,不然就会导致运算错误,得不到我们要的正确结果。(加运算没有影响)

举例:

//中缀表达式 3*(17-15)+18/6 的逆波兰表达式如下
String[] notation = {“3”, “17”, “15”, “-”, “*”, “18”, “6”, “/”, “+”};

我们是从左到右去遍历这个数组的遇到操作数压入栈中,当遇到第一个操作符时,此时栈是这样的如下图,此时我我们获取的第一个数o1是15,第二个数是o2是17(栈是先进后出的),所以我们要用第二个数去操作第二个数即o2(±*/)o1 就是17 -15
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_47254987/article/details/107634381
今日推荐