尚硅谷图解Java数据结构和算法.pdf
图解.xlsx

1、定义

  • 栈是一个先入后出的有序列表
  • 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶,另一端为固定的一端,称为栈底
  • 最先放入的元素在栈底,且最后出栈。最后放入的元素在栈顶,且最先出栈

三、栈 - 图1
image.png
image.png

2、应用场景

  • 子程序递归调用。如JVM中的虚拟机栈
  • 表达式转换(中缀转后缀)与求值
  • 二叉树的遍历
  • 图的深度优先遍历

image.png

3、实现

用数组实现

思路

  • 定义top表示栈顶,初始值为-1
  • 入栈的操作,先让top++,再放入数组
  • 出栈操作,先取出元素,在让top–
  • top == -1时,栈空
  • top == maxSize-1时,栈满

image.png
代码

  1. package com.atguigu.stack;
  2. import java.util.Scanner;
  3. public class ArrayStackDemo {
  4. public static void main(String[] args) {
  5. //测试一下ArrayStack 是否正确
  6. //先创建一个ArrayStack对象->表示栈
  7. ArrayStack stack = new ArrayStack(4);
  8. String key = "";
  9. boolean loop = true; //控制是否退出菜单
  10. Scanner scanner = new Scanner(System.in);
  11. while(loop) {
  12. System.out.println("show: 表示显示栈");
  13. System.out.println("exit: 退出程序");
  14. System.out.println("push: 表示添加数据到栈(入栈)");
  15. System.out.println("pop: 表示从栈取出数据(出栈)");
  16. System.out.println("请输入你的选择");
  17. key = scanner.next();
  18. switch (key) {
  19. case "show":
  20. stack.list();
  21. break;
  22. case "push":
  23. System.out.println("请输入一个数");
  24. int value = scanner.nextInt();
  25. stack.push(value);
  26. break;
  27. case "pop":
  28. try {
  29. int res = stack.pop();
  30. System.out.printf("出栈的数据是 %d\n", res);
  31. } catch (Exception e) {
  32. // TODO: handle exception
  33. System.out.println(e.getMessage());
  34. }
  35. break;
  36. case "exit":
  37. scanner.close();
  38. loop = false;
  39. break;
  40. default:
  41. break;
  42. }
  43. }
  44. System.out.println("程序退出~~~");
  45. }
  46. }
  47. //定义一个 ArrayStack 表示栈
  48. class ArrayStack {
  49. private int maxSize; // 栈的大小
  50. private int[] stack; // 数组,数组模拟栈,数据就放在该数组
  51. private int top = -1;// top表示栈顶,初始化为-1
  52. //构造器
  53. public ArrayStack(int maxSize) {
  54. this.maxSize = maxSize;
  55. stack = new int[this.maxSize];
  56. }
  57. //栈满
  58. public boolean isFull() {
  59. return top == maxSize - 1;
  60. }
  61. //栈空
  62. public boolean isEmpty() {
  63. return top == -1;
  64. }
  65. //入栈-push
  66. public void push(int value) {
  67. //先判断栈是否满
  68. if(isFull()) {
  69. System.out.println("栈满");
  70. return;
  71. }
  72. top++;
  73. stack[top] = value;
  74. }
  75. //出栈-pop, 将栈顶的数据返回
  76. public int pop() {
  77. //先判断栈是否空
  78. if(isEmpty()) {
  79. //抛出异常
  80. throw new RuntimeException("栈空,没有数据~");
  81. }
  82. int value = stack[top];
  83. top--;
  84. return value;
  85. }
  86. //显示栈的情况[遍历栈], 遍历时,需要从栈顶开始显示数据
  87. public void list() {
  88. if(isEmpty()) {
  89. System.out.println("栈空,没有数据~~");
  90. return;
  91. }
  92. //需要从栈顶开始显示数据
  93. for(int i = top; i >= 0 ; i--) {
  94. System.out.printf("stack[%d]=%d\n", i, stack[i]);
  95. }
  96. }
  97. }

4、应用

表达式求值

image.png
思路
image.png
三、栈 - 图8

  • 准备一个索引index来帮助我们遍历表达式
  • 如果index位置上的元素是一个数字,就直接入栈
  • 如果index位置上的元素是一个符号
    • 如果符号栈为空,直接入栈
    • 如果符号栈不为空
      • index位置上的符号的优先级小于或等于栈顶符号的优先级,则弹出两个数栈中的元素和符号栈中的一个符号,并且进行计算。将运算结果放入数栈中,并将index位置上的符号压入符号栈
      • index位置上的符号的优先级大于符号栈栈顶符号的优先级,则将该符号压入符号栈
  • 当表达式遍历完毕后,就弹出数栈中的2个数字和符号栈中的1个符号进行运算,并将运行结果入栈
  • 最终数栈中只有一个值,这个值便是运算结果
  • 注意:
    • 读取的是字符,所以存入数字前需要减去0的ASCII码
    • 如果数字是多位数,需要一直读,读到下一位不是数字为止,然后将读到的字符进行拼接,然后一起压入数栈

代码
存在的问题:因为栈是用的整型数组,所以计算除法的时候,无法转化成double

  1. package com.atguigu.stack;
  2. public class Calculator {
  3. public static void main(String[] args) {
  4. //根据前面老师思路,完成表达式的运算
  5. String expression = "7*2*2-5+1-5+3-4"; // 15//如何处理多位数的问题?
  6. //创建两个栈,数栈,一个符号栈
  7. ArrayStack2 numStack = new ArrayStack2(10);
  8. ArrayStack2 operStack = new ArrayStack2(10);
  9. //定义需要的相关变量
  10. int index = 0;//用于扫描
  11. int num1 = 0;
  12. int num2 = 0;
  13. int oper = 0;
  14. int res = 0;
  15. char ch = ' '; //将每次扫描得到char保存到ch
  16. String keepNum = ""; //用于拼接 多位数
  17. //开始while循环的扫描expression
  18. while(true) {
  19. //依次得到expression 的每一个字符
  20. ch = expression.substring(index, index+1).charAt(0);
  21. //判断ch是什么,然后做相应的处理
  22. if(operStack.isOper(ch)) {//如果是运算符
  23. //判断当前的符号栈是否为空
  24. if(!operStack.isEmpty()) {
  25. //如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,
  26. //在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈
  27. if(operStack.priority(ch) <= operStack.priority(operStack.peek())) {
  28. num1 = numStack.pop();
  29. num2 = numStack.pop();
  30. oper = operStack.pop();
  31. res = numStack.cal(num1, num2, oper);
  32. //把运算的结果如数栈
  33. numStack.push(res);
  34. //然后将当前的操作符入符号栈
  35. operStack.push(ch);
  36. } else {
  37. //如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈.
  38. operStack.push(ch);
  39. }
  40. }else {
  41. //如果为空直接入符号栈..
  42. operStack.push(ch); // 1 + 3
  43. }
  44. } else { //如果是数,则直接入数栈
  45. //numStack.push(ch - 48); //? "1+3" '1' => 1
  46. //分析思路
  47. //1. 当处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
  48. //2. 在处理数,需要向expression的表达式的index 后再看一位,如果是数就进行扫描,如果是符号才入栈
  49. //3. 因此我们需要定义一个变量 字符串,用于拼接
  50. //处理多位数
  51. keepNum += ch;
  52. //如果ch已经是expression的最后一位,就直接入栈
  53. if (index == expression.length() - 1) {
  54. numStack.push(Integer.parseInt(keepNum));
  55. }else{
  56. //判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入栈
  57. //注意是看后一位,不是index++
  58. if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))) {
  59. //如果后一位是运算符,则入栈 keepNum = "1" 或者 "123"
  60. numStack.push(Integer.parseInt(keepNum));
  61. //重要的!!!!!!, keepNum清空
  62. keepNum = "";
  63. }
  64. }
  65. }
  66. //让index + 1, 并判断是否扫描到expression最后.
  67. index++;
  68. if (index >= expression.length()) {
  69. break;
  70. }
  71. }
  72. //当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行.
  73. while(true) {
  74. //如果符号栈为空,则计算到最后的结果, 数栈中只有一个数字【结果】
  75. if(operStack.isEmpty()) {
  76. break;
  77. }
  78. num1 = numStack.pop();
  79. num2 = numStack.pop();
  80. oper = operStack.pop();
  81. res = numStack.cal(num1, num2, oper);
  82. numStack.push(res);//入栈
  83. }
  84. //将数栈的最后数,pop出,就是结果
  85. int res2 = numStack.pop();
  86. System.out.printf("表达式 %s = %d", expression, res2);
  87. }
  88. }
  89. //先创建一个栈,直接使用前面创建好
  90. //定义一个 ArrayStack2 表示栈, 需要扩展功能
  91. class ArrayStack2 {
  92. private int maxSize; // 栈的大小
  93. private int[] stack; // 数组,数组模拟栈,数据就放在该数组
  94. private int top = -1;// top表示栈顶,初始化为-1
  95. //构造器
  96. public ArrayStack2(int maxSize) {
  97. this.maxSize = maxSize;
  98. stack = new int[this.maxSize];
  99. }
  100. //增加一个方法,可以返回当前栈顶的值, 但是不是真正的pop
  101. public int peek() {
  102. return stack[top];
  103. }
  104. //栈满
  105. public boolean isFull() {
  106. return top == maxSize - 1;
  107. }
  108. //栈空
  109. public boolean isEmpty() {
  110. return top == -1;
  111. }
  112. //入栈-push
  113. public void push(int value) {
  114. //先判断栈是否满
  115. if(isFull()) {
  116. System.out.println("栈满");
  117. return;
  118. }
  119. top++;
  120. stack[top] = value;
  121. }
  122. //出栈-pop, 将栈顶的数据返回
  123. public int pop() {
  124. //先判断栈是否空
  125. if(isEmpty()) {
  126. //抛出异常
  127. throw new RuntimeException("栈空,没有数据~");
  128. }
  129. int value = stack[top];
  130. top--;
  131. return value;
  132. }
  133. //显示栈的情况[遍历栈], 遍历时,需要从栈顶开始显示数据
  134. public void list() {
  135. if(isEmpty()) {
  136. System.out.println("栈空,没有数据~~");
  137. return;
  138. }
  139. //需要从栈顶开始显示数据
  140. for(int i = top; i >= 0 ; i--) {
  141. System.out.printf("stack[%d]=%d\n", i, stack[i]);
  142. }
  143. }
  144. //返回运算符的优先级,优先级是程序员来确定, 优先级使用数字表示
  145. //数字越大,则优先级就越高.
  146. public int priority(int oper) {
  147. if(oper == '*' || oper == '/'){
  148. return 1;
  149. } else if (oper == '+' || oper == '-') {
  150. return 0;
  151. } else {
  152. return -1; // 假定目前的表达式只有 +, - , * , /
  153. }
  154. }
  155. //判断是不是一个运算符
  156. public boolean isOper(char val) {
  157. return val == '+' || val == '-' || val == '*' || val == '/';
  158. }
  159. //计算方法
  160. public int cal(int num1, int num2, int oper) {
  161. int res = 0; // res 用于存放计算的结果
  162. switch (oper) {
  163. case '+':
  164. res = num1 + num2;
  165. break;
  166. case '-':
  167. res = num2 - num1;// 注意顺序
  168. break;
  169. case '*':
  170. res = num1 * num2;
  171. break;
  172. case '/':
  173. res = num2 / num1;
  174. break;
  175. default:
  176. break;
  177. }
  178. return res;
  179. }
  180. }

5、前缀 中缀 后缀(逆波兰)表达式

前缀表达式(波兰表达式)

image.png
image.png

中缀

image.png

后缀(逆波兰表达式)

后缀表达式运算方法

  • 从左向右读取表达式
    • 遇到数字就压入栈中
    • 遇到运算符就弹出栈顶和次顶元素。用次顶元素 运算符 栈顶元素,并将运算结果压入栈中,直到栈为空,最终结果就是运算结果

image.png

逆波兰计算器

image.png
代码实现

  1. package com.luyi.DataStructures.stack;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import java.util.Stack;
  6. public class PolandNotation {
  7. public static void main(String[] args) {
  8. // 先定义一个 逆波兰表达式
  9. //(3+4)*5-6 => 3 4 + 5 * 6 -
  10. // 为了方便 中间用空格隔开
  11. String suffixExpression = "3 4 + 5 * 6 -";
  12. // 1 现将suffixExpression 放到一个ArrayList中
  13. // 2 将ArrayList 传递一个方法 遍历ArrayList 配合栈 完成计算
  14. List<String> rpnList=getListString(suffixExpression);
  15. System.out.println("rpnList:"+rpnList);
  16. System.out.println("结果为: "+calculator(rpnList) );
  17. }
  18. // 将一个逆波兰表达式 一次将数据和运算符放入一个 ArrayList中
  19. public static List<String> getListString(String suffixExpression) {
  20. // 将suffixExpression 按空格分隔
  21. String[] split = suffixExpression.split(" ");
  22. List<String> list = new ArrayList<String>(Arrays.asList(split));
  23. return list;
  24. }
  25. // 完成对逆波兰表达式的运算
  26. // 1.从左至右扫描,将 3 和 4 压入堆栈;
  27. // 2.遇到+运算符,因此弹出 4 和 3(4 为栈顶元素,3 为次顶元素),计算出 3+4 的值,得 7,再将 7 入栈;
  28. // 3.将 5 入栈;
  29. // 4.接下来是×运算符,因此弹出 5 和 7,计算出 7×5=35,将 35 入栈;
  30. // 5.将 6 入栈;
  31. // 6.最后是-运算符,计算出 35-6 的值,即 29,由此得出最终结果
  32. public static int calculator(List<String> ls) {
  33. // 创建一个栈 只需要一个
  34. Stack<String> stack = new Stack<>();
  35. // 遍历 ls
  36. for(String item : ls) {
  37. // 使用一个正则表达式取出数
  38. if(item.matches("\\d+")) { // 匹配的是多位数
  39. // 入栈
  40. stack.push(item);
  41. }else { // 如果是运算符
  42. // pop出两个数并运算
  43. int b = Integer.parseInt(stack.pop());
  44. int a = Integer.parseInt(stack.pop());
  45. int res = 0;
  46. switch (item) {
  47. case "+":
  48. res = a+b;
  49. break;
  50. case "-":
  51. res = a-b;
  52. break;
  53. case "*":
  54. res = a*b;
  55. break;
  56. case "/":
  57. res = a/b;
  58. break;
  59. default:
  60. throw new RuntimeException("符号有问题");
  61. }
  62. stack.push(res+"");
  63. }
  64. }
  65. return Integer.parseInt(stack.pop());
  66. }
  67. }

中缀表达式转后缀表达式

image.pngimage.pngimage.png
中缀表达式转后缀表达式

  • 从左向右读取中缀表达式,并且创建栈s队列q
  • 如果读到的元素的数字,就直接入队放入q中
  • 如果读到的是运算符(运算符判定)
    • 如果s为空,则将该运算符压入s
    • 如果s不为空
      • 如果该运算符为左括号,则直接压入s
      • 如果该运算符为右括号,则将s中的元素依次出栈并入队到q中,直到遇见左括号为止(括号不放入q中)
      • 如果该运算符的优先级高于s栈顶的运算符,则将该元素压入s
      • 如果该运算符的优先级小于等于s栈顶的运算符,则弹出s栈顶的元素,并将其放入q中,该运算符重新判定入栈操作(运算符判定步骤)
  • 如果中缀表达式已经读取完毕,则将s中的元素依次出栈,放入q中
  • q中的元素依次出队,该顺序即为后缀表达式

代码

  1. package com.luyi.DataStructures.stack;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import java.util.Stack;
  6. public class PolandNotation {
  7. public static void main(String[] args) {
  8. // 完成将中缀表达式转成后缀代达式的功能
  9. // 1. 1+((2+3)*4)-5 => 1 2 3 + 4 * + 5 -
  10. // 2. 因为直接对Str 进行操作不方便 因此 先将 1+((2+3)*4)-5 转成中缀表达式对应的List
  11. // 即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
  12. // 3. 将得到的中缀表达式的list => 后缀表达式的List
  13. // 4. ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] => ArrayList [1,2,3,+,4,*,+,5]
  14. String expression = "1+((2+3)*4)-5";
  15. System.out.println("中缀表达式:"+ toInfixExpression(expression));
  16. List<String> parseSuffixExpressList = parseSuffixExpressionList(toInfixExpression(expression));
  17. System.out.println("逆波兰表达式:" +parseSuffixExpressList );
  18. //结果
  19. System.out.println("expression:"+expression+"="+ calculator(parseSuffixExpressList));
  20. // 先定义一个 逆波兰表达式
  21. //(3+4)*5-6 => 3 4 + 5 * 6 -
  22. //(30+4)*5-6 => 30 4 + 5 * 6 -
  23. //4*5-8+60+8/2 => 4 5 * 8 - 60 + 8 2 / +
  24. // 为了方便 中间用空格隔开
  25. String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
  26. // 1 现将suffixExpression 放到一个ArrayList中
  27. // 2 将ArrayList 传递一个方法 遍历ArrayList 配合栈 完成计算
  28. List<String> rpnList=getListString(suffixExpression);
  29. System.out.println("rpnList:"+rpnList);
  30. System.out.println("结果为: "+calculator(rpnList) );
  31. }
  32. // 将 中缀表达式转成对应的List
  33. public static List<String> toInfixExpression(String s) {
  34. // 定义一个ArrayList 存放中缀表达式的内容
  35. List<String> arrayList = new ArrayList<String>();
  36. int i = 0;
  37. String str; // 做对多位数的拼接
  38. char c; // 每遍历到一个字符放入c中
  39. do {
  40. // 如果c是一个非数字 直接加入到ls
  41. if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) < 57 ){
  42. arrayList.add(c + "");
  43. i++;
  44. }else { // 如果是一个数 需要考虑多位数的问题
  45. str = ""; // str 置空
  46. while (i < s.length() && (s.charAt(i) >= 48 && s.charAt(i) <= 57)) {
  47. str += c;
  48. i++;
  49. }
  50. arrayList.add(str);
  51. }
  52. }while (i < s.length());
  53. return arrayList;
  54. }
  55. // 将得到的中缀表达式的list => 后缀表达式的List ls=ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
  56. public static List<String> parseSuffixExpressionList(List<String> ls) {
  57. // 定义两个栈
  58. Stack<String> s1 = new Stack<String>(); // 符号栈
  59. // 第二个栈 在整个转换过程中 没有pop操作, 而且后面我们号需要逆序输出 还要逆序输出 我们同 List<String> s2 代替
  60. // Stack<String> s2 = new Stack<String>();
  61. List<String> s2 = new ArrayList<String>();
  62. // 遍历ls
  63. for (String item : ls) {
  64. // 如果是一个数 加入s2
  65. if(item.matches("\\d+")) {
  66. s2.add(item);
  67. }else if (item.equals("(")) {
  68. s1.push(item);
  69. }else if (item.equals(")")) {
  70. //如果是右括号“)”,则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃
  71. while (!s1.peek().equals("(")){
  72. s2.add(s1.pop());
  73. }
  74. s1.pop(); // 弹出 (
  75. }else {
  76. // 当item的优先级 小于 s1栈顶的运算符的优先级 将 s1 栈顶的运算符弹出并加入到 s2 中,再次转到(4.1)与 s1 中新的栈顶运算符相比较
  77. while (s1.size() != 0 && (Operation.getValue(item) <= Operation.getValue(s1.peek()))) {
  78. s2.add(s1.pop());
  79. }
  80. // 还需要将Item入栈
  81. s1.push(item);
  82. }
  83. }
  84. // 将s1的剩余的运算符加入s2中
  85. while (s1.size() !=0){
  86. s2.add(s1.pop());
  87. }
  88. return s2; // 因为是存在list中 不用逆序 正常输出就是逆波兰表达式
  89. }
  90. // 将一个逆波兰表达式 一次将数据和运算符放入一个 ArrayList中
  91. public static List<String> getListString(String suffixExpression) {
  92. // 将suffixExpression 按空格分隔
  93. String[] split = suffixExpression.split(" ");
  94. List<String> list = new ArrayList<String>(Arrays.asList(split));
  95. return list;
  96. }
  97. // 完成对逆波兰表达式的运算
  98. // 1.从左至右扫描,将 3 和 4 压入堆栈;
  99. // 2.遇到+运算符,因此弹出 4 和 3(4 为栈顶元素,3 为次顶元素),计算出 3+4 的值,得 7,再将 7 入栈;
  100. // 3.将 5 入栈;
  101. // 4.接下来是×运算符,因此弹出 5 和 7,计算出 7×5=35,将 35 入栈;
  102. // 5.将 6 入栈;
  103. // 6.最后是-运算符,计算出 35-6 的值,即 29,由此得出最终结果
  104. public static int calculator(List<String> ls) {
  105. // 创建一个栈 只需要一个
  106. Stack<String> stack = new Stack<>();
  107. // 遍历 ls
  108. for(String item : ls) {
  109. // 使用一个正则表达式取出数
  110. if(item.matches("\\d+")) { // 匹配的是多位数
  111. // 入栈
  112. stack.push(item);
  113. }else { // 如果是运算符
  114. // pop出两个数并运算
  115. int b = Integer.parseInt(stack.pop());
  116. int a = Integer.parseInt(stack.pop());
  117. int res = 0;
  118. switch (item) {
  119. case "+":
  120. res = a+b;
  121. break;
  122. case "-":
  123. res = a-b;
  124. break;
  125. case "*":
  126. res = a*b;
  127. break;
  128. case "/":
  129. res = a/b;
  130. break;
  131. default:
  132. throw new RuntimeException("符号有问题");
  133. }
  134. stack.push(res+"");
  135. }
  136. }
  137. return Integer.parseInt(stack.pop());
  138. }
  139. }
  140. // 编写一个类 Operation 返回一个运算符 对应的优先级
  141. class Operation {
  142. private static int ADD = 1;
  143. private static int SUB = 1;
  144. private static int MUL = 2;
  145. private static int DIV = 2;
  146. // 写一个方法 返回一个优先级数字
  147. public static int getValue(String operation) {
  148. int result = 0;
  149. switch (operation){
  150. case "+":
  151. result = ADD;
  152. break;
  153. case "-":
  154. result = SUB;
  155. break;
  156. case "*":
  157. result = MUL;
  158. break;
  159. case "/":
  160. result = DIV;
  161. break;
  162. default:
  163. System.out.println("不存在该运算符");
  164. break;
  165. }
  166. return result;
  167. }
  168. }