一、栈

1. 运用场景

image.png
image.png

2. 代码实现

  1. package com.atguigu.stack;
  2. public class Calculator {
  3. public static void main(String[] args) {
  4. // 完成表达式的运算
  5. String expression = "3+2*6-2";
  6. // 创建2个栈,数栈和符号栈
  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 result = 0;
  15. String keepNum = null;
  16. char ch = ' '; //将每次扫描得到的char保存到ch中
  17. // 开始用while语句扫描expression
  18. while (true) {
  19. // 依次得到expression中的每一个字符
  20. ch = expression.substring(index, index + 1).charAt(0);
  21. // 判断是数字还是符号
  22. if (operStack.isOper(ch)) {
  23. // 如果是运算符,则需要判断当前的符号栈是否为空
  24. if (!operStack.isEmpty()) {
  25. // 对符号进行优先级比较
  26. if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
  27. num1 = numStack.pop();
  28. num2 = numStack.pop();
  29. oper = operStack.pop();
  30. result = numStack.cal(num1, num2, (char) oper);
  31. // 把运算的结果入数栈
  32. numStack.push(result);
  33. // 把当前的运算符入符号栈
  34. operStack.push(ch);
  35. } else {
  36. // 如果为空则直接入栈
  37. operStack.push(ch);
  38. }
  39. }
  40. } else {
  41. // 如果是数,则直接入数栈
  42. // 当是多位数时,不能发现是数就入数栈。需要向expression后再看一位,如果是数就继续扫描,如果是符号就入栈
  43. // 因此需要定义一个字符串变量
  44. keepNum += ch;
  45. if (index == expression.length() - 1) {
  46. numStack.push(Integer.parseInt(keepNum)); // 根据ASK码将字符转为数字
  47. } else {
  48. // 注意:只是往后看一位,不是index++
  49. if (!operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
  50. numStack.push(Integer.parseInt(keepNum)); // 根据ASK码将字符转为数字
  51. // 注意:keepNum需要清空
  52. keepNum = "";
  53. }
  54. // 让index+1并判断是否扫描到expression最后了
  55. index++;
  56. if (index >= expression.length()) {
  57. break;
  58. }
  59. }
  60. }
  61. // 当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行
  62. while (true) {
  63. // 如果符号栈为空,则计算到了最后,数栈中只有一个数字(即结果)
  64. if (operStack.isEmpty()) {
  65. break;
  66. }
  67. num1 = numStack.pop();
  68. num2 = numStack.cal(num1, num2, (char) oper);
  69. numStack.push(result);
  70. }
  71. System.out.printf("表达式%s=%d", expression, numStack.pop());
  72. }
  73. }
  74. // 数字栈
  75. static class ArrayStack2 {
  76. // 定义栈的大小
  77. private int maxSize;
  78. private int[] stack; // 数组模拟栈,int类型的元素
  79. private int top = -1; //栈底,初始化没有数据
  80. // 构造器
  81. public ArrayStack2(int maxSize) {
  82. this.maxSize = maxSize;
  83. }
  84. public boolean inFull() {
  85. return top == maxSize - 1;
  86. }
  87. public boolean isEmpty() {
  88. return top == -1;
  89. }
  90. // 入栈
  91. public void push(int value) {
  92. // 先判断栈满没满
  93. if (inFull()) {
  94. System.out.println("栈满!");
  95. return;
  96. }
  97. top++;
  98. stack[top] = value;
  99. }
  100. // 出栈
  101. public int pop() {
  102. // 实质:将栈顶的数据返回
  103. if (isEmpty()) {
  104. // 抛出异常
  105. throw new RuntimeException("空栈,没有数据");
  106. }
  107. int value = stack[top];
  108. return value;
  109. }
  110. // 遍历栈,从栈顶往下遍历
  111. public void list() {
  112. if (isEmpty()) {
  113. System.out.println("空栈,没有数据");
  114. return;
  115. }
  116. for (int i = top; i > 0; i--) {
  117. System.out.printf("stack[%d]=%d出栈\n!", i, stack[i]);
  118. }
  119. }
  120. // 返回运算符的优先级,优先级是程序员来定的。优先级使用数字来表示,数值越大,优先级越高
  121. public int priority(int oper) {
  122. if (oper == '*' || oper == '/') {
  123. return 1; // 乘法和除法的优先级高
  124. } else if (oper == '+' || oper == '-') {
  125. return 0; // 加法和减法的优先级底
  126. } else {
  127. return -1; // 假设当前的计算式中只有加减乘除
  128. }
  129. }
  130. // 判断是不是一个运算符
  131. public boolean isOper(char val) {
  132. return val == '*' || val == '/' || val == '+' || val == '-';
  133. }
  134. // 计算方法
  135. public int cal(int num1, int num2, char oper) {
  136. int res = 0; // res用于存放计算的结果
  137. switch (oper) {
  138. case '+':
  139. res = num1 + num2;
  140. break;
  141. case '-':
  142. res = num2 - num1;
  143. break;
  144. case '*':
  145. res = num1 * num2;
  146. break;
  147. case '/':
  148. res = num2 / num1;
  149. break;
  150. }
  151. return res;
  152. }
  153. // 栈满
  154. public int peek() {
  155. return stack[top];
  156. }
  157. }
  158. }

3. 栈的前缀、中缀和后缀表达式(逆波兰表达式)

3.1 前缀:波兰表达式

3.2 后缀:逆波兰表达式

  1. package com.atguigu.stack;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.Stack;
  5. public class PolandNotationDemo {
  6. public static void main(String[] args) {
  7. // 先定义一个逆波兰表达式
  8. // (3+4)*5-6 ==> 3 4 + 5 * 6 -
  9. String suffixExpresson = "3 4 + 5 * 6 -";
  10. List<String> rpnList = getListString(suffixExpresson);
  11. }
  12. /**
  13. * 从最开始的方法--扫描字符串,改为将suffixExpression放入一个ArrayList中
  14. * step2: 通过配合栈的形式,完成运算
  15. */
  16. // 将逆波兰表达式依次放入一个ArrayList中
  17. public static List<String> getListString(String suffixExpresson) {
  18. // 将suffixExpressopm进行分割
  19. String[] splits = suffixExpresson.split(" ");
  20. List<String> list = new ArrayList<String>();
  21. for (String item : splits) {
  22. list.add(item);
  23. }
  24. return list;
  25. }
  26. // 完成计算
  27. public static int cal(List<String> ls) {
  28. // 创建一个栈
  29. Stack<String> stack = new Stack<String>();
  30. for (String item : ls) {
  31. // 使用正则表达式取数
  32. if (item.matches("\\d+")) {
  33. // 匹配的是多位数
  34. stack.push(item);
  35. } else {
  36. // pop出2个数并运算,然后入栈
  37. int num2 = Integer.parseInt(stack.pop());
  38. int num1 = Integer.parseInt(stack.pop());
  39. int res = 0;
  40. if (item.equals("+")) {
  41. res = num1 + num2;
  42. } else if (item.equals("-")) {
  43. res = num2 - num1;
  44. } else if (item.equals("*")) {
  45. res = num1 * num2;
  46. } else if (item.equals("/")) {
  47. res = num2 / num1;
  48. } else{
  49. throw new RuntimeException("运算符有问题!");
  50. }
  51. // 把res入栈
  52. stack.push(""+res);
  53. }
  54. }
  55. return Integer.parseInt(stack.pop());
  56. }
  57. }

4. 如何将中缀表达式转为后缀表达式

image.png

// 将中缀表达式转为后缀表达式
    public List<String> parseSuffixExpressionList(List<String> ls){
        // 定义2个栈
        Stack<String> s1= new Stack<String>();  // 符号栈
        // 说明:因为s2 这个栈,在这个转换过程中,没有pop操作,而且后面我们还需要逆序输出,因此比较麻烦
        // 这里我们不用栈的结构,直接使用List
//        Stack<String> s2= new Stack<String>();
        List<String> s2 = new ArrayList<String>();
        // 遍历ls
        for (String item : ls) {
            // 如果是一个数,就加入到s2
            if(item.matches("\\d+")){
                s2.add(item);
            }else if(item.equals("(")){
                s1.push(item);
            }else if(item.equals("(")){
                // 如果是右括号,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
                while(!s1.peek().equals(")")){
                    s2.add(s1.pop());
                }
                s1.pop();  // 消除左括号
            }else{
                // 当item的优先级小于等于s1栈顶运算符,将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符进行比较
                // 优先级比较高低的方法
                while(s1.size()!=0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){
                    s2.add(s1.pop());
                }
                // 还需要将item压入栈
                s1.push(item);
            }
            // 将s1的元素加入到s2中
            while (s1.size()!=0){
                s2.add(s1.pop());
            }
            return s2;  //因为是存放到List,因此按顺序存入就是逆波兰表达式
        }
        return s2;
    }

`比较运算符优先级的类:

// 编写比较运算符高低的类
class Operation{
    // 加减乘除的优先级
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;
    // 写一个方法返回对应优先级的数字
    public static int getValue(String operation){
        int result = 0;
        switch (operation) {
            case "+":
                result=ADD;
                break;
            case "-":
                result=SUB;
                break;
            case "*":
                result=MUL;
                break;
            case "/":
                result=DIV;
                break;
            default:
                System.out.println("不存在该运算符!");
                break;
        }
        return result;
    }
}