数据结构--栈

一、栈介绍

1)栈的英文为(stack)

2) 栈是一个先入后出(FILO-First In Last Out)的有序列表。

3)(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)

4) 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除

5) 图解方式说明出栈(pop)和入栈(push)的概念

二、栈的应用场景

1) 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。

2) 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。

3) 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。

4) 二叉树的遍历。

5) 图形的深度优先(depth 一 first)搜索法。

三、快速入门

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

2) 实现思路分析,并画出示意图

3)代码实现

服务类

 1 package serve;
 2 
 3 public class ArrayStackServe {
 4 
 5     public int maxSize;
 6     public int[] stack;
 7     public int top = -1;
 8 
 9     //构造器
10     public ArrayStackServe(int maxSize){
11         stack = new int[this.maxSize = maxSize];
12     }
13     //栈满
14     public boolean isNotFull(){
15        return maxSize-1 > top;
16     }
17     //栈空
18     public boolean isEmpty(){
19         return top == -1;
20     }
21     //入栈
22     public void pushValue(int value){
23         if(isNotFull()){
24             top++;
25             stack[top] = value;
26         }else {
27             System.out.println("栈满");
28             return;
29         }
30     }
31 
32     //出栈
33     public int popValue(){
34         if(isEmpty()){
35             throw new RuntimeException("栈已为空");
36         }
37         int value = stack[top];
38         top--;
39         return value;
40     }
41     //遍历栈
42     public void list(){
43         if(isEmpty()){
44             System.out.println("栈空");
45         }
46         for(int i = top;i>=0;i--){
47             System.out.printf("stack[%d] = %d \n",i,stack[i]);
48         }
49     }
50 
51 }

测试类

public class ArrayStackDemo {
    public static void main(String[] args) {
        ArrayStackServe arrayStackServe = new ArrayStackServe(10);
        arrayStackServe.pushValue(1);
        arrayStackServe.pushValue(2);
        arrayStackServe.pushValue(3);
        arrayStackServe.pushValue(4);
        arrayStackServe.list();
        System.out.println("————————————————————————————————————————————————");
        System.out.println(arrayStackServe.popValue());
    }
}

结果

stack[3] = 4 
stack[2] = 3 
stack[1] = 2 
stack[0] = 1 
————————————————————————————————————————————————
4

四、算法题

1、栈计算器(用后缀表达式-逆波兰表达式)

我们完成一个逆波兰计算器,要求完成如下任务:

1) 输入一个逆波兰表达式(后缀表达式),使用栈(Stack), 计算其结果

2) 支持小括号和多位数整数,因为这里我们主要讲的是数据结构,因此计算器进行简化,只支持对整数的计算。

1) 思路分析

例如:(3+4)×5-6 对应的后缀表达式就是 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、将表达式转换为集合

2、对集合进行遍历:

①如果是数字,入栈

②如果是符号,弹栈,配合符号执行运算操作,并将结果入栈

3、最后对结果return

服务类

package serve;

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

public class PolandNotationServe {

    //将一个表达式字符转换为ArrayList,后续遍历集合获取数据便可
    public List<String> turnExpression(String Expression){
        String[] spi = Expression.split(" ");
        return new ArrayList<>(Arrays.asList(spi));
    }
    //对集合进行遍历获取每个值并执行相应的操作
    /**
     * 思路:
     * 1、遇到数字就入栈
     * 2、遇到符号就弹栈两个值进行计算并将结果入栈
     */
    public int calculationList(List<String> strValue){
        Stack<String> stack = new Stack<>();
        //循环遍历集合并将数字字符转换为数字
        for (String item : strValue) {
            if(item.matches("\\d+")){//正则表达式判断是否为数字
                //是为数字,执行入栈
                stack.push(item);
            }else {
                //不是数字,执行弹栈
                int num1 = Integer.parseInt(stack.pop());
                int num2 = Integer.parseInt(stack.pop());
                int result = 0;
                //根据运算符执行运算操作,并将结果入栈
                if(item.equals("+")){
                    result = num1 + num2;
                }else if(item.equals("-")){
                    result = num2 - num1;
                }else if(item.equals("*")){
                    result = num1 * num2;
                }else if(item.equals("/")){
                    result = num2 / num1;
                }
                stack.push(result+"");
            }
        }
        return Integer.parseInt(stack.pop());

    }

}

测试类

 1 package mainProject;
 2 
 3 
 4 import serve.PolandNotationServe;
 5 
 6 import java.util.List;
 7 
 8 //逆波兰表达式实现计算器
 9 public class PolandNotationDemo {
10     public static void main(String[] args) {
11         PolandNotationServe polandNotationServe = new PolandNotationServe();
12         List<String> strings = polandNotationServe.turnExpression("3 4 + 5 * 6 -");
13         System.out.println(polandNotationServe.calculationList(strings));
14 
15     }
16 }

2、中缀表达式转后缀表达式

后缀表达式适合计算机进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转成后缀表达式。

1、 具体步骤如下:

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 中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

2、举例说明

将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式的过程如下

因此结果为 :"1 2 3 + 4 × + 5 –"

3、代码实现中缀表达式转为后缀表达式
  1 package mainProject.TurnToPostfix;
  2 
  3 import java.beans.Expression;
  4 import java.util.ArrayList;
  5 import java.util.List;
  6 import java.util.Stack;
  7 
  8 //中缀表达式转前缀表达式
  9 
 10 /**
 11  * 1、将中缀表达式字符串转换为集合
 12  * 3、符号优先级比较器
 13  * 2、将中缀表达式集合转换为后缀表达式集合
 14  */
 15 public class TurnPostfixDemo {
 16 
 17     public static void main(String[] args) {
 18         List<String> list = toInfixExpressionList("1+((20+30)*4)-5");
 19         System.out.println("中缀表达式集合"+list);
 20 
 21         //System.out.println(Operation.getValue("/"));
 22         System.out.println("后缀表达式集合"+turnPostfixExpression(list));
 23 
 24     }
 25 
 26 
 27     //方法:将中缀表达式字符串转换成对应的List
 28     public static List<String> toInfixExpressionList(String expression){
 29 
 30         List<String> list = new ArrayList<>();
 31         int i = 0;
 32         StringBuilder str; // 对多位数的拼接
 33         char c;
 34         do{
 35             //如果是符号直接保存,如果是数字,获取足够的数字再保存
 36             if((c =expression.charAt(i))<48 || (c = expression.charAt(i)) >57){
 37             //说明是符号
 38                 c = expression.charAt(i);
 39                 list.add(c+"");
 40                 i++;
 41             }else {
 42                 str = new StringBuilder();//执行拼接前对先前多位数进行清空
 43                 //否则为数字,是数字就就循环获取十位数、百位数等的数字
 44                 while(i<expression.length() && (c = expression.charAt(i)) >= 48 && (c = expression.charAt(i)) <= 57){
 45                    //为什么还需要i<i<expression.length(),不添加时报空指针异常,是在字符串最后一个字符进行处理。
 46                     str.append(c);
 47                    i++;
 48                 }
 49                 list.add(str.toString());
 50             }
 51 
 52         }while (i < expression.length());
 53         return list;
 54     }
 55 
 56     //方法:将中缀表达式转换成后缀表达式
 57     public static List<String> turnPostfixExpression(List<String> list){
 58         //需要两个栈S1(存储符号)、S2(存储结果)
 59         Stack<String> s1 = new Stack<>();
 60         //说明:因为 s2 这个栈,在整个转换过程中,没有 pop 操作,而且后面我们还需要逆序输出
 61         //因此比较麻烦,这里我们就不用 Stack<String> 直接使用 List<String> s2
 62         //Stack<String> s2 = new Stack<>();
 63         List<String> s2 = new ArrayList<>();
 64         for (String item : list) {
 65             //如果为操作数,就放入s2中
 66             if(item.matches("\\d+")){
 67                 s2.add(item);
 68             }else if(item.equals("(")){
 69                 //如果是(,直接入栈
 70                 s1.push(item);
 71             }else if(item.equals(")")){
 72                 //如果是),将里面的内容弹栈,直到栈顶遇到(就停止
 73                 while(!s1.peek().equals("(")){
 74                     s2.add(s1.pop());
 75                 }
 76                 //消除掉"("
 77                 s1.pop();
 78             }else{
 79                 //比较运算符优先级 当 item 的优先级小于等于 s1 栈顶运算符, 将 s1 栈顶的运算符弹出并加入到 s2 中
 80             while(s1.size()!=0 && Operation.getValue(item)<=Operation.getValue(s1.peek())){
 81                 s2.add(s1.pop());
 82             }
 83             //还需要将 item 压入栈
 84             s1.push(item);
 85             }
 86         }
 87         //将 s1 中剩余的运算符依次弹出并加入 s2
 88         while(s1.size()!=0){
 89             s2.add(s1.pop());
 90         }
 91         return s2;
 92     }
 93 
 94 }
 95 
 96 //编写一个Operation类,可以返回一个运算符对应的优先级
 97 class Operation{
 98     private static int ADD = 1;
 99     private static int SUB = 1;
100     private static int MUL = 2;
101     private static int DIV = 2;
102 
103     //写一个方法,根据字符返回优先级数字
104     public static int getValue(String operation){
105         int num = 0;
106         switch (operation){
107             case "+":num = ADD;break;
108             case "-":num = SUB;break;
109             case "*":num = MUL;break;
110             case "/":num = DIV;break;
111             default:
112                 System.out.println("该符号不存在");
113                 break;
114         }
115         return num;
116     }
117 
118 }
4、总结

有几个点值得学习

1、将字符串进行分割

2、优先级判断

3、正则表达式获取字符串中是数字“\\d+”

4、中缀转后缀的五个步骤:(前提是已将中缀表达式字符串转为了集合,方便遍历)

  • ①一个存放符号栈s1、一个存放结果集合s2*

  • ②遇到数字存放在结果集合s2中

  • ③遇到括号分析处理

1、"(":入栈s1

2、")":s1内容出栈进入到s2,指到遇到"(",最后记得将")"弹出

  • ④遇到符号分析处理

1、比较符号优先级:新的优先级低,将s1出栈,进入到s2,直到该新符号优先级比栈顶符号优先级高,并将自己入栈到s1。(就是说新的符号优先级比s1栈顶的高,就将该符号压入到s1中)

  • ⑤将剩下的符号出栈到s2中。

 

 

 

posted @ 2022-05-06 21:18  jason饼干大怪兽  阅读(175)  评论(0)    收藏  举报