栈的一个实际需求

请输入一个表达式
计算式:[722-5+1-5+3-3] 点击计算
image.png
请问:计算机底层是如何运算得到结果的?
注意不是简单的把算式列出运算,因为我们看这个算式很简单
但是计算机怎么理解这个算式的(对计算机而言,它接收到的就是一个字符串),我们讨论的是这个问题。

栈的介绍

1)栈的英文为(stack)
2)栈是一个先入后出(FILO-First In Last Out)的有序列表。
3)栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的
一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
4)根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元
素最先删除,最先放入的元素最后删除
5)图解方式说明出栈(pop)和入栈(push)的概念尚硅谷Java数据结构和算法

栈的应用场景

  1. 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
  2. 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
  3. 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
  4. 二叉树的遍历。
  5. 图形的深度优先(depth一first)搜索法。

    栈的快速入门

  6. 用数组模拟栈的使用,由于栈是一种有序列表,当然可以使用数组的结构来储存栈的数据内容,
    下面我们就用数组模拟栈的出栈,入栈等操作。

  7. 实现思路分析,并画出示意图

    数组实现

    1. /**
    2. * 数组实现
    3. * 0.存储数据
    4. * 1.构造器
    5. * 2.满
    6. * 3.空
    7. * 4.push
    8. * 5.pop
    9. * 6.遍历
    10. */
    11. public class ArrayStack {
    12. private int[] array;
    13. private int size;
    14. private int top=-1;
    15. public ArrayStack(int size) {
    16. this.size = size;
    17. array = new int[size];
    18. }
    19. public int getSize() {
    20. return size;
    21. }
    22. public boolean isFull(){
    23. return top == size - 1;
    24. }
    25. public boolean isEmpty(){
    26. return top == -1;
    27. }
    28. public void push(int val) {
    29. if (isFull()) {
    30. throw new RuntimeException("栈已满,无法push");
    31. }
    32. top++;
    33. array[top] = val;
    34. }
    35. public int pop(){
    36. if (isEmpty()) {
    37. throw new RuntimeException("栈是空的,无法pop");
    38. }
    39. int result = array[top];
    40. top--;
    41. return result;
    42. }
    43. public void show(){
    44. for (int i = top; i >=0 ; i--) {
    45. System.out.printf("stack[%d]=%d\n",i,array[i]);
    46. }
    47. }
    48. public static void main(String[] args) {
    49. ArrayStack arrayStack = new ArrayStack(5);
    50. arrayStack.push(1);
    51. arrayStack.push(2);
    52. arrayStack.push(3);
    53. arrayStack.push(4);
    54. arrayStack.push(5);
    55. arrayStack.show();
    56. System.out.println(arrayStack.pop());
    57. System.out.println(arrayStack.pop());
    58. System.out.println(arrayStack.pop());
    59. }
    60. }

    第一次用双链表

    ```java package com.sgg.datastructure.stack;

/**

  • 链表实现
  • 0.存储数据
  • 1.构造器
  • 2.满
  • 3.空
  • 4.push
  • 5.pop
  • 6.遍历 */ public class LinkStackTwo { private StackNodeTwo top;

    public boolean isFull() {

    1. return false;

    }

    public boolean isEmpty() {

    1. return top == null;

    }

    public void push(int i) {

    1. if (top == null) {
    2. top = new StackNodeTwo(i);
    3. top.next = top;
    4. top.prev = top;
    5. } else {
    6. StackNodeTwo node = new StackNodeTwo(i);
    7. top.next = node;
    8. node.prev = top;
    9. top = node;
    10. }

    }

    public int pop() {

    1. if (isEmpty()) {
    2. throw new RuntimeException("空了");
    3. }
    4. int no = top.no;
    5. if (top != top.prev) {
    6. top = top.prev;
    7. } else {
    8. top = null;
    9. }
    10. return no;

    }

    public void show() {

    1. StackNodeTwo temp = top;
    2. while (true) {
    3. System.out.printf("遍历%d\n",temp.no);
    4. temp = temp.prev;
    5. if (temp.prev == temp) {
    6. System.out.println("遍历"+temp.no);
    7. break;
    8. }
    9. }

    }

    public static void main(String[] args) {

    1. LinkStackTwo linkStack = new LinkStackTwo();
    2. linkStack.push(1);
    3. linkStack.push(2);
    4. linkStack.push(3);
    5. linkStack.push(4);
    6. linkStack.show();
    7. System.out.println(linkStack.pop());
    8. System.out.println(linkStack.pop());
    9. System.out.println(linkStack.pop());
    10. System.out.println(linkStack.pop());

    } }

  1. <a name="Adk4u"></a>
  2. ## 第二次看别人用单链表更合理
  3. ```java
  4. package com.sgg.datastructure.stack;
  5. /**
  6. * 链表实现
  7. * 0.存储数据
  8. * 1.构造器
  9. * 2.满
  10. * 3.空
  11. * 4.push
  12. * 5.pop
  13. * 6.遍历
  14. */
  15. public class LinkStackOne {
  16. private StackNodeOne top;
  17. public boolean isEmpty() {
  18. return top == null;
  19. }
  20. public void push(int i) {
  21. StackNodeOne node = new StackNodeOne(i);
  22. node.next = top;
  23. top = node;
  24. }
  25. public int pop() {
  26. if (isEmpty()) {
  27. throw new RuntimeException("空");
  28. }
  29. int no = top.no;
  30. top = top.next;
  31. return no;
  32. }
  33. public void show() {
  34. StackNodeOne temp = top;
  35. while (temp != null) {
  36. System.out.println("loop" + temp.no);
  37. temp = temp.next;
  38. }
  39. }
  40. public static void main(String[] args) {
  41. LinkStackOne linkStack = new LinkStackOne();
  42. linkStack.push(1);
  43. linkStack.push(2);
  44. linkStack.push(3);
  45. linkStack.push(4);
  46. linkStack.show();
  47. System.out.println(linkStack.pop());
  48. System.out.println(linkStack.pop());
  49. System.out.println(linkStack.pop());
  50. linkStack.push(5);
  51. linkStack.push(6);
  52. linkStack.push(7);
  53. linkStack.show();
  54. }
  55. }

利用栈,实现计算器

中缀直接计算

思路

1.遍历字符串,把数字和操作符分别放到2个栈
image.png
2.数字就是无脑入栈
3.操作符入栈有以下几种情况

  • 操作符栈顶为空
  • 当前操作符的优先级小于或者等于操作符栈顶的,取出栈顶,pop2个数,计算,入数栈,当前操作符入栈
  • 优先级大于,入栈

4.pop运算
5.只剩下一个就行

代码实现

代码有bug 先不管到时候用其他的实现
src=http---img3.doubanio.com-view-group_topic-l-public-p293455180.jpg&refer=http---img3.doubanio.jpg

后缀表达式

思路

3 4 + 5 × 6 -的计算
1.从左至右扫描,将3和4压入堆栈;
2.遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素,注意与前缀表达式做比较),计算出3+4的值,得7,再将7入栈;
3.将5入栈;
4.接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
5.将6入栈;
6.最后是-运算符,计算出35-6的值,即29,由此得出最终结果。

代码

  1. public class 后缀表达式计算器 {
  2. public static String cal(String expression) {
  3. Stack<String> stack = new Stack<>();
  4. List<String> list = StrUtil.split(expression, StrUtil.SPACE);
  5. for (String str : list) {
  6. if (NumberUtil.isNumber(str)) {
  7. stack.push(str);
  8. }else {
  9. Integer pop1 = Integer.valueOf(stack.pop());
  10. Integer pop2 = Integer.valueOf(stack.pop());
  11. Integer result = calOne(pop1, pop2, str);
  12. stack.push(result+"");
  13. }
  14. }
  15. String result = stack.pop();
  16. System.out.printf("后缀表达式:%s = %s\n",expression,result);
  17. return result;
  18. }
  19. private static Integer calOne(Integer pop1, Integer pop2, String str) {
  20. Integer result = null;
  21. switch (str) {
  22. case "+":
  23. result = pop2 + pop1;
  24. break;
  25. case "-":
  26. result = pop2 - pop1;
  27. break;
  28. case "*":
  29. result = pop2 * pop1;
  30. break;
  31. case "/":
  32. result = pop2 / pop1;
  33. break;
  34. default:
  35. throw new RuntimeException("参数有误");
  36. }
  37. return result;
  38. }
  39. public static void main(String[] args) {
  40. String expression = "3 4 + 5 * 6 -";//29
  41. cal(expression);
  42. String expression2 = "4 5 * 8 - 60 + 8 2 / +";//76
  43. cal(expression2);
  44. }
  45. }

结果

后缀表达式:3 4 + 5 6 - = 29
后缀表达式:4 5
8 - 60 + 8 2 / + = 76
用后缀表达式计算结果还是很简单的

中缀表达式->后缀表达式

思路

  1. 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
  2. 从左至右扫描中缀表达式;
  3. 遇到操作数时,将其压s2;
  4. 遇到运算符时,比较其与s1栈顶运算符的优先级:
    1. 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    2. 否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    3. 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;
  5. 遇到括号时:
    (1)如果是左括号“(”,则直接压入s1
    (2)如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  6. 重复步骤2至5,直到表达式的最右边
  7. 将s1中剩余的运算符依次弹出并压入s2
  8. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

    代码

    ```java package com.sgg.datastructure.stack;

import cn.hutool.core.util.StrUtil;

import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Stack;

public class 表达式 { public static List 中缀转后缀(String expression) { Stack operate = new Stack<>(); List result = new ArrayList<>();//可以用arraylist替代 List split = toInfixExpressionList(expression);//没有考虑超过2位数的数字拼接 for (String str : split) { if (StrUtil.isNumeric(str)) { result.add(str); } else if (isOperate(str)) { while (true) { if (operate.isEmpty() || StrUtil.equals(operate.peek(), “(“)) { operate.push(str); break; } else if (isPriority(str, operate.peek())) { operate.push(str); break; } else { String pop = operate.pop(); result.add(pop); } } } else if (StrUtil.equals(“(“, str)) { operate.push(str); } else if (StrUtil.equals(“)”, str)) { String ele; while (true) { ele = operate.pop(); if (StrUtil.equals(ele, “(“)) { break; } else { result.add(ele); } } } else { throw new RuntimeException(“参数有误”); } } while (!operate.isEmpty()) { String pop = operate.pop(); result.add(pop); } return result; }

  1. private static boolean isPriority(String str, String peek) {
  2. return getScore(str) > getScore(peek);
  3. }
  4. private static int getScore(String str) {
  5. switch (str) {
  6. case "+":
  7. case "-":
  8. return 1;
  9. case "*":
  10. case "/":
  11. return 2;
  12. default:
  13. throw new RuntimeException("参数有误");
  14. }
  15. }
  16. public static boolean isOperate(String str) {
  17. List<String> list = Arrays.asList("+", "-", "*", "/");
  18. return list.contains(str);
  19. }
  20. public static List<String> toInfixExpressionList(String expression) {
  21. List<String> result = new ArrayList<>();
  22. String ele = "";
  23. String[] split = StrUtil.split(expression, 1);
  24. for (String raw : split) {
  25. if (StrUtil.isNumeric(raw)) {
  26. ele += raw;
  27. } else {
  28. if (StrUtil.isNotBlank(ele)) {
  29. result.add(ele);
  30. ele = "";
  31. }
  32. result.add(raw);
  33. }
  34. }
  35. if (StrUtil.isNotBlank(ele)) {
  36. result.add(ele);
  37. }
  38. return result;
  39. }
  40. public static void main(String[] args) {
  41. String middle = "1+((2+3)*41)-20";
  42. List<String> ex = 中缀转后缀(middle);
  43. System.out.println(ex);
  44. System.out.println(后缀表达式计算器.cal(StrUtil.join(StrUtil.SPACE, ex)));
  45. }

}

```

总结

思路挺难的,是数学和计算机思维,主要学应用和思想!