栈是一种基于先进后出(FILO)的数据结构(队列是先进先出),是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
我们称数据进入到栈的动作为压栈,数据从栈中出去的动作为弹栈。
类名 | 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:节点类 |
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
public boolean isEmpty(){
return length==0;
}
//2.获取栈中的元素个数
public int size(){
return length;
}
//3.弹出栈顶元素
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;
}
}
}
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());
}
}
要搞清楚逆波兰表达式,我们要先搞清楚中缀表达式
中缀表达式就是我们生活中使用的表达式,例如:1+3*2,2-(1+3)等等,中缀表达式的特点是:二元运算符总是置于两个操作数中间。
中缀表达式是人们最喜欢的表达式方式,因为简单,易懂。但是对于计算机来说就不是这样了,因为中缀表达式的运算顺序不具有规律性。不同的运算符具有不同的优先级,如果计算机执行中缀表达式,需要解析表达式语义,做大量的优先级相关操作。
逆波兰表示法是波兰逻辑学家J・卢卡西维兹(J・ Lukasewicz)于1929年首先提出的一种表达式的表示方法,后缀表达式的特点:运算符总是放在跟它相关的操作数之后。
中缀表达式 | 后缀表达式 |
---|---|
a+b | ab+ |
a+(b-c) | abc-+ |
a+(b-c)*d | abc-d*+ |
a*(b-c)+d | abc-*d+ |
或许看图还是不是很清楚这个是怎么计算的。那我就再来解释一下:
其实很简单,操作符的前面两个数就是这个操作符的两个操作数,我们用上面表格的4个表达式举例:
需求: 给定一个只包含加减乘除四种运算的逆波兰表达式的数组表示方式,求出该逆波兰表达式的结果。
//中缀表达式 3*(17-15)+18/6 的逆波兰表达式如下
String[] notation = {“3”, “17”, “15”, “-”, “*”, “18”, “6”, “/”, “+”};
实现思路
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;
}
}
我们在第四步对栈中弹出的两个数进行运算时,要注意这个两个数(o1,o2)的顺序,不然就会导致运算错误,得不到我们要的正确结果。(加运算没有影响)
举例:
//中缀表达式 3*(17-15)+18/6 的逆波兰表达式如下
String[] notation = {“3”, “17”, “15”, “-”, “*”, “18”, “6”, “/”, “+”};
我们是从左到右去遍历这个数组的遇到操作数压入栈中,当遇到第一个操作符时,此时栈是这样的如下图,此时我我们获取的第一个数o1是15,第二个数是o2是17(栈是先进后出的),所以我们要用第二个数去操作第二个数即o2(±*/)o1 就是17 -15