基础数据结构-栈 - 图1

1. 什么是栈?

1.1 栈的基本概念

栈是一种特殊的线性表结构,它具有后进先出/先进后出的特性(Last In First Out/First In Last Out),栈只能从一端进行数据元素的插入和删除操作,插入元素可称之为压栈、入栈、进栈,出栈入栈的一端被称为栈顶,另一端为栈底。
基础数据结构-栈 - 图2

1.2 栈的特点

  • 是一种特殊的线性表。
  • 先进后出/后进先出。
  • 只能从一端删除元素。

    2. 栈的实现

    因为栈是一种特殊的线性表结构,所以从线性表就可以联想到数组与链表,而栈也可以说成是一种特殊的数组或链表,因为栈可以基于数组、链表实现。

    2.1 基于数组实现栈

    基于数组的方式实现栈以数组的头部为栈底,以从数组的头部到尾部为栈的生长方向。
    image.png ```java package com.muzili.stack;

/*

  • @author: muzili(李敷斌)
  • @date: 2021/7/20
  • @version: v0.0.1
  • @description: 基于数组实现栈 */ public class ArrayStack implements Stack{

    /**

    • 保存数组的栈 */ private T[] stackArr;

      /**

    • 容量 */ private int capcity = 20;

      /**

    • 栈实际大小 */ private int size = 0;

      public ArrayStack() { stackArr = (T[]) new Object[capcity]; }

      public ArrayStack(int capcity) { this.capcity = capcity; stackArr = (T[]) new Object[capcity]; }

      /**

    • 入栈 *
    • @param item */ public void push(T item) { //判断大小是否足够 如果不够进行扩容 judgeSize(); stackArr[size++] = item; }

      /**

    • 判断大小 */ private void judgeSize() {

      if (size >= capcity){

      1. //如果实际大小+1大于或等于容量大小 则需要进行扩容
      2. capcity = capcity * 2;
      3. resize(capcity);

      }else if (size > 0 && size > 20 && size <= capcity / 2){

      1. capcity = capcity / 2;
      2. resize(capcity);

      }

      }

      /**

    • 改变数组大小
    • @param capcity */ public void resize(int capcity){

      T[] temp = (T[]) new Object[capcity]; int count = capcity > this.capcity ? this.capcity:capcity; for (int i = 0; i < count; i++) {

      1. temp[i] = stackArr[i];

      } stackArr = temp; }

      /**

    • 出栈 *
    • @return */ public T pop() {

      if (isEmpty()){

      1. return null;

      } T item = stackArr[—size]; stackArr[size] = null; judgeSize(); return item; }

      /**

    • 是否为空 *
    • @return */ public boolean isEmpty() { return size == 0; }

}

  1. <a name="Di4LA"></a>
  2. ## 2.2 基于链表实现栈
  3. 基于链表的方式实现栈,以链表表头为栈低,入栈的元素插入链表尾部,通过链表实现便于元素的插入和删除。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21857252/1626750238886-0d038391-a81e-4412-a3a4-c3f0a711926b.png#align=left&display=inline&height=69&margin=%5Bobject%20Object%5D&name=image.png&originHeight=69&originWidth=319&size=3496&status=done&style=none&width=319)
  4. ```java
  5. package com.muzili.stack;
  6. import com.muzili.list.LinkedList;
  7. /**
  8. *
  9. * @author: muzili(李敷斌)
  10. * @date: 2021/7/20
  11. * @version: v0.0.1
  12. * @description: 基于LinkedList实现栈
  13. */
  14. public class LinkedListStack<T> implements Stack<T>{
  15. /**
  16. * 保存数据的链表
  17. */
  18. private LinkedList<T> itemList = new LinkedList<T>();
  19. /**
  20. * 入栈
  21. *
  22. * @param item
  23. */
  24. public void push(T item) {
  25. itemList.addLast(item);
  26. }
  27. /**
  28. * 出栈
  29. *
  30. * @return
  31. */
  32. public T pop() {
  33. T data = itemList.getLast().getData();
  34. itemList.removeLast();
  35. return data;
  36. }
  37. /**
  38. * 是否为空
  39. *
  40. * @return
  41. */
  42. public boolean isEmpty() {
  43. return itemList.getSize() == 0;
  44. }
  45. }

2.3 数组实现VS链表实现

区别:

  • 链表实现起来更加简单。
  • 数组需要有扩容操作,否则可能会导致栈溢出。
  • 链表有天然的自动扩容的优势。

3. 栈的使用场景

  • 函数调用
    • 比如Java语言中函数调用,后调用的方法会先执行完毕,这就是一个典型的栈结构。
  • 数学表达式计算
    • https://www.cnblogs.com/leon0812/articles/1211638.html
    • 比如这样一个表达式2-3*5/6-2,计算机无法一步识别出计算的先后顺序,而通过栈就可以解决这个问题,使用两个栈,一个栈存数值,一个栈存计算符号,如果压入符号时,当前待压入的符号优先级大于栈顶符号则直接入栈,否则出栈。 ```java package com.muzili.stack;

import java.math.BigDecimal; import java.util.Arrays;

/**

  • 数学表达式计算 *
  • 使用两个栈实现
  • 一个栈存储数值 一个栈存储符号 *
  • 规则数值入栈 如果入栈的时符号与栈顶的符号比对 如果栈顶的符号优先级低于当前符号将符号压入栈
  • 如果栈顶符号优先级高于或等于待入栈符号 则栈顶符号出栈 取两个数值进行计算 然后重复以上步骤 *
  • step 1 如果入栈的是一个符号 与栈顶进行对比
  • step 2 如果待入栈符号优先级高于栈顶符号 直接入栈,否则栈顶元素出栈 然后再取出两个数值进行运算 运算结果再入栈
  • step 3 重复上述步骤 *
  • 符号 优先级
  • ( -1
      • 0
    • / 1
  • ) 2 / -1(当比较的对象是(为-1)
  • 如 2+35+6+(1+53)+1 *
  • tip: 把 (,)当做特殊的运算符 相邻的两个可以抵消 *
    1. 2入栈 +号入栈,因为符号栈栈顶为null直接入栈
    1. 3入栈 与栈顶元素比较,比+号的优先级高,入栈
    1. 5入栈 +与栈顶符号比较 +比的优先级小,*出栈,数值出栈5 3,
    1. 进行运算,运算结果压入数值栈3 * 5 = 15
    1. +号在与栈顶符号+对比,结果与栈顶符号相等,优先出现的符号先计算,出栈+号 数值出栈2 15
    1. 2 + 15 = 17,运算结果入栈,此时栈顶为null +号直接入栈符号栈
    1. 6入栈,符号+ 与栈顶符号+比较 两个符号优先级相等 栈顶符号+出栈 数值栈 17,6出栈
    1. 17 + 6 = 23运算结果入栈,+号直接入栈
    1. 符号 (入栈,数值1入栈
    1. 符号+与栈顶符号(比较 栈顶符号(优先级小于+,+号入栈,数值5入栈
    1. 符号与栈顶符号+比较 栈顶符号优先级小 符号*入栈,数值3入栈
    1. 符号)与栈顶符号比较 栈顶符号优先级高于) 出栈符号 出栈数值3 5
    1. 5 * 3 = 15运算结果入栈,符号)与栈顶符号比较 栈顶符号优先级高于),栈顶符号出栈 + 数值出栈 15 3
    1. 1 + 15 = 16运算结果入栈,符号)与栈顶符号比较 栈顶符号优先级与符号)相等,栈顶符号出栈 因为是特殊符号所以不取数值
    1. ()相互抵消,+号与栈顶符号+比较 大小相等,栈顶符号出栈 + , 数值出栈 16 23
    1. 23 + 16 = 39运算结果入栈,栈顶没有符号了 符号+直接入栈
    1. 数值1入栈,所有符号入栈完毕,出栈符号 + 出栈数值 1 39
    1. 39 + 1 = 40 40
  • @author: muzili(李敷斌)
  • @date: 2021/7/20
  • @version: v0.0.1
  • @description: 数学表达式计算 */ public class MathElCalcu {

    /**

    • 声明数值栈 */ Stack numStack = new ArrayStack();

      /**

    • 声明符号栈 */ Stack operatorStack = new ArrayStack();

      /**

    • 运算结果 */ BigDecimal result = BigDecimal.ZERO;

      /**

    • 运算符 */ Character operator;
  1. Character[] operatorArr = {'+','-','*','/','(',')'};
  2. /**
  3. * 计算
  4. * @param str
  5. * @return 计算结果
  6. */
  7. public BigDecimal calc(String str){
  8. if (str == null || str.equals("")){
  9. throw new IllegalArgumentException("算术表达式不能为空!");
  10. }
  11. //判断括号是否匹配
  12. BracketMatch bracketMatch = new BracketMatch();
  13. if (!bracketMatch.isOk(str)) {
  14. throw new IllegalArgumentException("算术表达式错误!"+str);
  15. }
  16. //TODO 更多输入校验
  17. char[] chars = str.toCharArray();
  18. for (Character c : chars) {
  19. if (!Arrays.asList(operatorArr).contains(c)){
  20. numStack.push(BigDecimal.valueOf(Double.parseDouble(String.valueOf(c))));
  21. continue;
  22. }
  23. pushOperator(c);
  24. }
  25. BigDecimal num2 = numStack.pop();
  26. BigDecimal num1 = numStack.pop();
  27. operator = operatorStack.pop();
  28. BigDecimal result = calc(num1, num2, operator);
  29. return result;
  30. }
  31. /**
  32. * 运算符入栈
  33. * @param c
  34. */
  35. private void pushOperator(Character c) {
  36. operator = operatorStack.pop();
  37. //如果栈顶没有元素 或 栈顶元素优先级低于当前符号直接入栈
  38. Character judgeSize = judgeSize(operator, c);
  39. if (operator == null || judgeSize.equals('<')){
  40. operatorStack.push(operator);
  41. operatorStack.push(c);
  42. return;
  43. }
  44. if (judgeSize.equals('=') && operator.equals('(') && c.equals(')')){
  45. return;
  46. }
  47. BigDecimal num2 = numStack.pop();
  48. BigDecimal num1 = numStack.pop();
  49. BigDecimal result = calc(num1, num2, operator);
  50. numStack.push(result);
  51. pushOperator(c);
  52. }
  53. /**
  54. * 计算
  55. * @param num1
  56. * @param num2
  57. * @param operator
  58. * @return 计算结果
  59. */
  60. public BigDecimal calc(BigDecimal num1,BigDecimal num2,Character operator){
  61. switch (operator){
  62. case '+':
  63. result = num1.add(num2);
  64. System.out.println(num1+"+"+num2+"="+result);
  65. break;
  66. case '-':
  67. result = num1.subtract(num2);
  68. System.out.println(num1+"-"+num2+"="+result);
  69. break;
  70. case '*':
  71. result = num1.multiply(num2);
  72. System.out.println(num1+"*"+num2+"="+result);
  73. break;
  74. case '/':
  75. result = num1.divide(num2);
  76. System.out.println(num1+"/"+num2+"="+result);
  77. break;
  78. }
  79. return result;
  80. }
  81. /**
  82. * 判断大小
  83. * @param operator 栈顶符号
  84. * @param currOperator 当前符号 待入栈符号
  85. * @return 结果
  86. */
  87. public Character judgeSize(Character operator,Character currOperator){
  88. if(operator == null){
  89. return '<';
  90. }
  91. switch (operator){
  92. case '+':
  93. case '-':
  94. switch (currOperator){
  95. case '*':
  96. case '/':
  97. case '(':
  98. return '<';
  99. case '+':
  100. case '-':
  101. case ')':
  102. return '>';
  103. }
  104. case '*':
  105. case '/':
  106. switch (currOperator){
  107. case '(':
  108. return '<';
  109. case '+':
  110. case '-':
  111. case '*':
  112. case '/':
  113. case ')':
  114. return '>';
  115. }
  116. case '(':
  117. switch (currOperator){
  118. case '+':
  119. case '-':
  120. case '*':
  121. case '/':
  122. return '<';
  123. case '(':
  124. case ')':
  125. return '=';
  126. }
  127. case ')':
  128. switch (currOperator){
  129. case '+':
  130. case '-':
  131. case '*':
  132. case '/':
  133. case ')':
  134. return '>';
  135. }
  136. }
  137. return '<';
  138. }
  139. public static void main(String[] args) {
  140. MathElCalcu mathElCalcu = new MathElCalcu();
  141. System.out.println(mathElCalcu.calc("2+3*5+6+(1+5*3)+1"));
  142. }

}

运行结果》》》 “D:\Program Files\Java\jdk1.8.0_191\bin\java.exe” “-javaagent:E:\Program Files\JetBrains\IntelliJ IDEA 2019.1.1\lib\idea_rt.jar=53003:E:\Program Files\JetBrains\IntelliJ IDEA 2019.1.1\bin” -Dfile.encoding=UTF-8 -classpath “D:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_191\jre\lib\rt.jar;D:\workspace\learning\data-structure\target\classes” com.muzili.stack.MathElCalcu 3.05.0=15.00 2.0+15.00=17.00 17.00+6.0=23.00 5.03.0=15.00 1.0+15.00=16.00 23.00+16.00=39.00 39.00+1.0=40.00 40.00

  1. - IDE符号匹配
  2. ```java
  3. package com.muzili.stack;
  4. import java.util.Scanner;
  5. /**
  6. *
  7. * @author: muzili(李敷斌)
  8. * @date: 2021/7/20
  9. * @version: v0.0.1
  10. * @description: 通过栈实现括号匹配
  11. */
  12. public class BracketMatch {
  13. Stack<Character> leftBracketStack = new ArrayStack<Character>();
  14. /**
  15. * 判断是否完全匹配
  16. * @param str
  17. * @return 是否完全匹配
  18. */
  19. public boolean isOk(String str){
  20. if (str == null || str.equals("")){
  21. return false;
  22. }
  23. char[] charArray = str.toCharArray();
  24. for (char c : charArray) {
  25. Character character;
  26. switch (c){
  27. case '{':
  28. case '[':
  29. case '(':
  30. leftBracketStack.push(c);
  31. break;
  32. case '}':
  33. character = leftBracketStack.pop();
  34. if (character == null || !character.equals('{')){
  35. return false;
  36. }
  37. break;
  38. case ']':
  39. character = leftBracketStack.pop();
  40. if (character == null || !character.equals('[')){
  41. return false;
  42. }
  43. break;
  44. case ')':
  45. character = leftBracketStack.pop();
  46. if (character == null || !character.equals('(')){
  47. return false;
  48. }
  49. break;
  50. default:
  51. break;
  52. }
  53. }
  54. return leftBracketStack.isEmpty();
  55. }
  56. public static void main(String[] args) {
  57. Scanner scanner = new Scanner(System.in);
  58. while (scanner.hasNext()){
  59. BracketMatch bracketMatch = new BracketMatch();
  60. System.out.println("输入的内容是否匹配:"+bracketMatch.isOk(scanner.nextLine()));
  61. }
  62. }
  63. }
  • 浏览器前进后退功能