栈与队列不同,队列是先进先出,栈是先进后出。
栈也可以使用 数组和 链表实现。

一般的我们都会使用链表来实现,谁没事干用数组啊。

思路

比如说我们现在有一个节点 top,这个节点就类似于 我们之前讲单链表时的头节点。
只不过这里我们把它成虚拟的栈顶节点

入栈

因为栈是先进后出的结构,所以我们采取头插法比较合适。
至于头插法,我们在单链表中 进行反转链表的时候讲过了 -> 快速传送

要注意的是,如果是第一个元素的话就不必采用头插法,可以直接top.next = 新节点
但如果不是第一元素的话,就要采取头插法了
新节点 node
node.next = top.next
top.next = node

出栈

出栈的时候就更简单了,你在链表中删除第一个节点(也就是栈顶节点)就行了
top.next = top.next.next

一般的我们的栈的标配的几个方法是

  • 出栈
  • 入栈
  • 遍历
  • 获取栈顶元素,但不出栈
  • 获取栈中元素的个数

所以我们可以在 栈中加一个 int 型变量size 代表元素的个数。

代码实现

基本的节点信息:

  1. class Node {
  2. private Object data;
  3. private Node next;
  4. //省略掉了 getter setter
  5. }

栈的代码:

  1. public class LinkStack {
  2. //栈内元素个数
  3. private int size;
  4. //栈顶虚拟节点
  5. private Node top = new Node("");
  6. //判断栈是否为null
  7. public boolean isEmpety() {
  8. return size == 0;
  9. }
  10. //入栈,使用头插法
  11. public void push(Object value) {
  12. Node node = new Node(value);
  13. if (isEmpety()) {
  14. top.setNext(node);
  15. }else {
  16. node.setNext(top.getNext());
  17. top.setNext(node);
  18. }
  19. size++;
  20. }
  21. //获取栈顶元素,但是不出栈
  22. public Object top() {
  23. if (isEmpety()) {
  24. System.out.println("栈为空");
  25. return null;
  26. }
  27. return top.getNext().getData();
  28. }
  29. //栈顶元素出栈
  30. public Object pop() {
  31. if (isEmpety()) {
  32. return null;
  33. }
  34. Node node = top.getNext();
  35. top.setNext(top.getNext().getNext());
  36. size--;
  37. return node.getData();
  38. }
  39. //遍历栈内元素,但不出栈
  40. public void list() {
  41. if (isEmpety()) {
  42. System.out.println("栈空");
  43. return;
  44. }
  45. Node temp = top.getNext();
  46. while (temp != null) {
  47. System.out.println(temp.getData());
  48. temp = temp.getNext();
  49. }
  50. }
  51. //获取栈内元素个数
  52. public int getSize() {
  53. return this.size;
  54. }
  55. }

这个只是为了看起来比较好理解,但实际用的时候并不适用?每次pop值得时候都要强转太麻烦了。
于是,考虑加入泛型

带泛型的代码

  1. public class LinkStack<T> {
  2. //栈内元素个数
  3. private int size;
  4. //栈顶虚拟节点
  5. private Node<T> top = new Node("");
  6. //判断栈是否为null
  7. public boolean isEmpety() {
  8. return size == 0;
  9. }
  10. //入栈,使用头插法
  11. public void push(T value) {
  12. Node<T> node = new Node(value);
  13. if (isEmpety()) {
  14. top.setNext(node);
  15. }else {
  16. node.setNext(top.getNext());
  17. top.setNext(node);
  18. }
  19. size++;
  20. }
  21. //获取栈顶元素,但是不出栈
  22. public T top() {
  23. if (isEmpety()) {
  24. System.out.println("栈为空");
  25. return null;
  26. }
  27. return top.getNext().getData();
  28. }
  29. //栈顶元素出栈
  30. public T pop() {
  31. if (isEmpety()) {
  32. return null;
  33. }
  34. Node<T> node = top.getNext();
  35. top.setNext(top.getNext().getNext());
  36. size--;
  37. return node.getData();
  38. }
  39. //遍历栈内元素,但不出栈
  40. public void list() {
  41. if (isEmpety()) {
  42. System.out.println("栈空");
  43. return;
  44. }
  45. Node<T> temp = top.getNext();
  46. while (temp != null) {
  47. System.out.println(temp.getData());
  48. temp = temp.getNext();
  49. }
  50. }
  51. //获取栈内元素个数
  52. public int getSize() {
  53. return this.size;
  54. }
  55. }
  56. class Node<T> {
  57. private T data;
  58. private Node<T> next;
  59. public Node(T data) {
  60. this.data = data;
  61. }
  62. public T getData() {
  63. return data;
  64. }
  65. public void setData(T data) {
  66. this.data = data;
  67. }
  68. public Node<T> getNext() {
  69. return next;
  70. }
  71. public void setNext(Node<T> next) {
  72. this.next = next;
  73. }
  74. }

逆波兰表达式计算(后缀表达式计算)

其实私底下,用了中缀表达式来实现了,但是太过麻烦了,还要考虑优先级和括号,还不好理解。
而后缀呢,实现起来简单,但是并不符合我们人的思维需要中缀转一下后缀,而且还可以很好的处理括号问题。

(3 + 4) 5 - 6 中缀转后缀 -> 3 4 + 5 6 - ,这里我先手动转一下。至于如何转,会在下文讲解。
计算思路:
后缀表达式是从左到右进行扫描

  • 如果是数字的话,直接入栈。
  • 如果是操作符的话,从栈中pop两次得到两个数值,然后进行计算。
  1. public class PolandNotation {
  2. public static void main(String[] args) {
  3. //使用后缀表达式(逆波兰)来进行 表达式的计算
  4. //为了区别 多位数,我们使用空格来将 符合和数值分割
  5. String expr = "3 4 + 5 * 6 -";
  6. //相比于中缀和前缀,后缀表达式更为简单,也更符合我们和计算机的思维
  7. List<String> strings = processExpr(expr);
  8. System.out.println(strings);
  9. float result = getResult(strings);
  10. System.out.println(result);
  11. }
  12. public static List<String> processExpr(String expr) {
  13. String[] split = expr.split(" ");
  14. List<String> list = new ArrayList<>();
  15. //将分割的数组加入到 list中
  16. Arrays.stream(split).forEach(list::add);
  17. return list;
  18. }
  19. public static float getResult(List<String> expr) {
  20. LinkStack<Float> stack = new LinkStack<>();
  21. //开始扫描 表达式
  22. expr.forEach(v -> {
  23. //首先判断是数字还是字符
  24. if (!isOperator(v)) {
  25. //不是符号的话直接入栈
  26. stack.push(Float.parseFloat(v));
  27. }else {
  28. //是字符的话,从栈中pop出两个数值,然后进行计算
  29. float res = cal(stack.pop(), stack.pop(), v);
  30. //再将计算出的数值入栈
  31. stack.push(res);
  32. }
  33. });
  34. return stack.pop();
  35. }
  36. public static boolean isOperator(String val) {
  37. return val.equals("+") ||
  38. val.equals("-") ||
  39. val.equals("*") ||
  40. val.equals("/");
  41. }
  42. public static float cal(float num1, float num2, String oper) {
  43. float res = 0;
  44. switch (oper) {
  45. case "+" :
  46. res = num2 + num1;
  47. break;
  48. case "-" :
  49. res = num2 - num1;
  50. break;
  51. case "*" :
  52. res = num2 * num1;
  53. break;
  54. case "/" :
  55. res = num2 / num1;
  56. break;
  57. }
  58. return res;
  59. }
  60. }

中缀转后缀

中缀转后缀的时候需要有两个栈,这里称为 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 -

加入 中缀转后缀后,代码实现

  1. package cn.edu.zzuli.stack;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.Collection;
  5. import java.util.List;
  6. import java.util.regex.PatternSyntaxException;
  7. public class PolandNotation {
  8. public static void main(String[] args) {
  9. //使用后缀表达式(逆波兰)来进行 表达式的计算
  10. //为了区别 多位数,我们使用空格来将 符合和数值分割
  11. String nifix = "1 + ( ( 2 + 3 ) * 4 ) - 5";
  12. //String expr = "3 4 + 5 * 6 -";
  13. List<String> expr = nifixToPostfix(processExpr(nifix));
  14. //System.out.println(expr);
  15. float result = getResult(expr);
  16. System.out.println(result);
  17. }
  18. public static List<String> nifixToPostfix(List<String> nifix) {
  19. //存放符号
  20. LinkStack<String> stack = new LinkStack<>();
  21. //用来代替栈s2,存放结果,因为我们的 s2栈并没有pop操作,所以可以代替。
  22. List<String> postfix = new ArrayList<>();
  23. nifix.forEach(v -> {
  24. //判断是符号还是数字
  25. if (isOperator(v)) {
  26. //如果栈为空 或者 栈顶为 (
  27. if (stack.isEmpety() || stack.top().equals("(") || v.equals("(")) {
  28. stack.push(v);
  29. }
  30. else if (v.equals(")")) {
  31. //依次弹出
  32. while (!stack.top().equals("(")) {
  33. postfix.add(stack.pop());
  34. }
  35. //弹出 (
  36. stack.pop();
  37. }
  38. else{
  39. //判断优先级 <= 得情况
  40. while (!stack.isEmpety() && priority(v) <= priority(stack.top())) {
  41. postfix.add(stack.pop());
  42. }
  43. //此时栈顶有元素的话,已经比当前运算符的优先级高了
  44. stack.push(v);
  45. }
  46. }else {
  47. //数字直接入栈
  48. postfix.add(v);
  49. }
  50. });
  51. while (!stack.isEmpety()) {
  52. postfix.add(stack.pop());
  53. }
  54. return postfix;
  55. }
  56. public static List<String> processExpr(String expr) {
  57. String[] split = expr.split(" ");
  58. List<String> list = new ArrayList<>();
  59. //将分割的数组加入到 list中
  60. Arrays.stream(split).forEach(list::add);
  61. return list;
  62. }
  63. public static float getResult(List<String> expr) {
  64. LinkStack<Float> stack = new LinkStack<>();
  65. //开始扫描 表达式
  66. expr.forEach(v -> {
  67. //首先判断是数字还是字符
  68. if (!isOperator(v)) {
  69. //不是符号的话直接入栈
  70. stack.push(Float.parseFloat(v));
  71. }else {
  72. //是字符的话,从栈中pop出两个数值,然后进行计算
  73. float res = cal(stack.pop(), stack.pop(), v);
  74. //再将计算出的数值入栈
  75. stack.push(res);
  76. }
  77. });
  78. return stack.pop();
  79. }
  80. public static int priority(String oper) {
  81. if (oper.equals("*") || oper.equals("/")) {
  82. return 10;
  83. } else if (oper.equals("+") || oper.equals("-")) {
  84. return 5;
  85. }
  86. return -1;
  87. }
  88. public static boolean isOperator(String val) {
  89. return val.equals("+") ||
  90. val.equals("-") ||
  91. val.equals("*") ||
  92. val.equals("/") ||
  93. val.equals("(") ||
  94. val.equals(")");
  95. }
  96. public static float cal(float num1, float num2, String oper) {
  97. float res = 0;
  98. switch (oper) {
  99. case "+" :
  100. res = num2 + num1;
  101. break;
  102. case "-" :
  103. res = num2 - num1;
  104. break;
  105. case "*" :
  106. res = num2 * num1;
  107. break;
  108. case "/" :
  109. res = num2 / num1;
  110. break;
  111. }
  112. return res;
  113. }
  114. }