栈与队列不同,队列是先进先出,栈是先进后出。
栈也可以使用 数组和 链表实现。
思路
比如说我们现在有一个节点 top,这个节点就类似于 我们之前讲单链表时的头节点。
只不过这里我们把它成虚拟的栈顶节点
入栈
因为栈是先进后出的结构,所以我们采取头插法比较合适。
至于头插法,我们在单链表中 进行反转链表的时候讲过了 -> 快速传送
要注意的是,如果是第一个元素的话就不必采用头插法,可以直接top.next = 新节点
但如果不是第一元素的话,就要采取头插法了
新节点 node
node.next = top.next
top.next = node
出栈
出栈的时候就更简单了,你在链表中删除第一个节点(也就是栈顶节点)就行了
top.next = top.next.next
一般的我们的栈的标配的几个方法是
- 出栈
- 入栈
- 遍历
- 获取栈顶元素,但不出栈
- 获取栈中元素的个数
所以我们可以在 栈中加一个 int 型变量size 代表元素的个数。
代码实现
基本的节点信息:
class Node {private Object data;private Node next;//省略掉了 getter setter}
栈的代码:
public class LinkStack {//栈内元素个数private int size;//栈顶虚拟节点private Node top = new Node("");//判断栈是否为nullpublic boolean isEmpety() {return size == 0;}//入栈,使用头插法public void push(Object value) {Node node = new Node(value);if (isEmpety()) {top.setNext(node);}else {node.setNext(top.getNext());top.setNext(node);}size++;}//获取栈顶元素,但是不出栈public Object top() {if (isEmpety()) {System.out.println("栈为空");return null;}return top.getNext().getData();}//栈顶元素出栈public Object pop() {if (isEmpety()) {return null;}Node node = top.getNext();top.setNext(top.getNext().getNext());size--;return node.getData();}//遍历栈内元素,但不出栈public void list() {if (isEmpety()) {System.out.println("栈空");return;}Node temp = top.getNext();while (temp != null) {System.out.println(temp.getData());temp = temp.getNext();}}//获取栈内元素个数public int getSize() {return this.size;}}
这个只是为了看起来比较好理解,但实际用的时候并不适用?每次pop值得时候都要强转太麻烦了。
于是,考虑加入泛型
带泛型的代码
public class LinkStack<T> {//栈内元素个数private int size;//栈顶虚拟节点private Node<T> top = new Node("");//判断栈是否为nullpublic boolean isEmpety() {return size == 0;}//入栈,使用头插法public void push(T value) {Node<T> node = new Node(value);if (isEmpety()) {top.setNext(node);}else {node.setNext(top.getNext());top.setNext(node);}size++;}//获取栈顶元素,但是不出栈public T top() {if (isEmpety()) {System.out.println("栈为空");return null;}return top.getNext().getData();}//栈顶元素出栈public T pop() {if (isEmpety()) {return null;}Node<T> node = top.getNext();top.setNext(top.getNext().getNext());size--;return node.getData();}//遍历栈内元素,但不出栈public void list() {if (isEmpety()) {System.out.println("栈空");return;}Node<T> temp = top.getNext();while (temp != null) {System.out.println(temp.getData());temp = temp.getNext();}}//获取栈内元素个数public int getSize() {return this.size;}}class Node<T> {private T data;private Node<T> next;public Node(T data) {this.data = data;}public T getData() {return data;}public void setData(T data) {this.data = data;}public Node<T> getNext() {return next;}public void setNext(Node<T> next) {this.next = next;}}
逆波兰表达式计算(后缀表达式计算)
其实私底下,用了中缀表达式来实现了,但是太过麻烦了,还要考虑优先级和括号,还不好理解。
而后缀呢,实现起来简单,但是并不符合我们人的思维需要中缀转一下后缀,而且还可以很好的处理括号问题。
(3 + 4) 5 - 6 中缀转后缀 -> 3 4 + 5 6 - ,这里我先手动转一下。至于如何转,会在下文讲解。
计算思路:
后缀表达式是从左到右进行扫描
- 如果是数字的话,直接入栈。
- 如果是操作符的话,从栈中pop两次得到两个数值,然后进行计算。
public class PolandNotation {public static void main(String[] args) {//使用后缀表达式(逆波兰)来进行 表达式的计算//为了区别 多位数,我们使用空格来将 符合和数值分割String expr = "3 4 + 5 * 6 -";//相比于中缀和前缀,后缀表达式更为简单,也更符合我们和计算机的思维List<String> strings = processExpr(expr);System.out.println(strings);float result = getResult(strings);System.out.println(result);}public static List<String> processExpr(String expr) {String[] split = expr.split(" ");List<String> list = new ArrayList<>();//将分割的数组加入到 list中Arrays.stream(split).forEach(list::add);return list;}public static float getResult(List<String> expr) {LinkStack<Float> stack = new LinkStack<>();//开始扫描 表达式expr.forEach(v -> {//首先判断是数字还是字符if (!isOperator(v)) {//不是符号的话直接入栈stack.push(Float.parseFloat(v));}else {//是字符的话,从栈中pop出两个数值,然后进行计算float res = cal(stack.pop(), stack.pop(), v);//再将计算出的数值入栈stack.push(res);}});return stack.pop();}public static boolean isOperator(String val) {return val.equals("+") ||val.equals("-") ||val.equals("*") ||val.equals("/");}public static float cal(float num1, float num2, String oper) {float res = 0;switch (oper) {case "+" :res = num2 + num1;break;case "-" :res = num2 - num1;break;case "*" :res = num2 * num1;break;case "/" :res = num2 / num1;break;}return res;}}
中缀转后缀
中缀转后缀的时候需要有两个栈,这里称为 s1 , s2
先对 (3 + 4) * 5 - 6 进行扫描
如果是数字的话,直接压入 s2
如果是字符的话
1.1 如果栈中为空,或者栈顶元素位 ( 那么直接压入 s1。
1.2 如果栈不为空,栈顶元素位 ) ,那么从s1 pop 一个运算符 压入 s2,直到 pop出来的运算符位 ( 为止,此时消去了 一对 ()
1.3 如果当前字符 优先级高于 栈顶元素的话 直接入 s1,否则跳到1.4
1.4 当前运算符的优先级 小于等于 栈顶元素。 s1 pop 出运算符到 s2中,直到当前运算符高于 栈顶运算符或者栈为空的情况,新的运算符入栈。
如果遍历完毕表达式
那么将 s1 中的所有元素 依次pop出,然后压入到 s2 中,此时 s2 从栈顶到栈底,就是 后缀表达式的倒序。
看一下例子:
当操作符是 ( + ) -
s1 ( —> s1 ( + —> s1 —> s1 —> s1 - —> s1
s2 3 s2 3 4 s2 3 4 + s2 3 4 + 5 s2 3 4 + 5 6 s2 3 4 + 5 6 -
加入 中缀转后缀后,代码实现
package cn.edu.zzuli.stack;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.List;import java.util.regex.PatternSyntaxException;public class PolandNotation {public static void main(String[] args) {//使用后缀表达式(逆波兰)来进行 表达式的计算//为了区别 多位数,我们使用空格来将 符合和数值分割String nifix = "1 + ( ( 2 + 3 ) * 4 ) - 5";//String expr = "3 4 + 5 * 6 -";List<String> expr = nifixToPostfix(processExpr(nifix));//System.out.println(expr);float result = getResult(expr);System.out.println(result);}public static List<String> nifixToPostfix(List<String> nifix) {//存放符号LinkStack<String> stack = new LinkStack<>();//用来代替栈s2,存放结果,因为我们的 s2栈并没有pop操作,所以可以代替。List<String> postfix = new ArrayList<>();nifix.forEach(v -> {//判断是符号还是数字if (isOperator(v)) {//如果栈为空 或者 栈顶为 (if (stack.isEmpety() || stack.top().equals("(") || v.equals("(")) {stack.push(v);}else if (v.equals(")")) {//依次弹出while (!stack.top().equals("(")) {postfix.add(stack.pop());}//弹出 (stack.pop();}else{//判断优先级 <= 得情况while (!stack.isEmpety() && priority(v) <= priority(stack.top())) {postfix.add(stack.pop());}//此时栈顶有元素的话,已经比当前运算符的优先级高了stack.push(v);}}else {//数字直接入栈postfix.add(v);}});while (!stack.isEmpety()) {postfix.add(stack.pop());}return postfix;}public static List<String> processExpr(String expr) {String[] split = expr.split(" ");List<String> list = new ArrayList<>();//将分割的数组加入到 list中Arrays.stream(split).forEach(list::add);return list;}public static float getResult(List<String> expr) {LinkStack<Float> stack = new LinkStack<>();//开始扫描 表达式expr.forEach(v -> {//首先判断是数字还是字符if (!isOperator(v)) {//不是符号的话直接入栈stack.push(Float.parseFloat(v));}else {//是字符的话,从栈中pop出两个数值,然后进行计算float res = cal(stack.pop(), stack.pop(), v);//再将计算出的数值入栈stack.push(res);}});return stack.pop();}public static int priority(String oper) {if (oper.equals("*") || oper.equals("/")) {return 10;} else if (oper.equals("+") || oper.equals("-")) {return 5;}return -1;}public static boolean isOperator(String val) {return val.equals("+") ||val.equals("-") ||val.equals("*") ||val.equals("/") ||val.equals("(") ||val.equals(")");}public static float cal(float num1, float num2, String oper) {float res = 0;switch (oper) {case "+" :res = num2 + num1;break;case "-" :res = num2 - num1;break;case "*" :res = num2 * num1;break;case "/" :res = num2 / num1;break;}return res;}}
