[java]-算法与数据结构-第五章-栈

五、栈 Stack

1.栈的实际需求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nu1BZd2P-1649582859474)(https://secure2.wostatic.cn/static/cne8Ts5vSxmfUG59GtyoD/image.png)]

2. 栈的介绍

  1. 栈的英文为(stack)
  2. 栈是一个先入后出(FILOfirst In Last Out)的有序列表。
  3. 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一^种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另—端为固定的一端,称为栈底(Bottom)。
  4. 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而
    删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
  5. 出栈(pop)入栈(push)的概念

在这里插入图片描述

3. 栈的应用场景

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

4. 栈的思路分析

1)分析

  1. 使用数组来模拟栈
  2. 定义一个 top 来表示栈顶,初始化 为 -1
  3. 入栈的操作,当有数据加入到栈时, top++; stack[top] = data;
  4. 出栈的操作, int value = stack[top]; top–, return value

2)数组模拟栈

测试方法

public static void testArrayStack(){
    
    
        ArrayStack arrayStack = new ArrayStack(4);
        String key = "";
        boolean loop = true;
        Scanner scanner = new Scanner(System.in);
        while (loop){
    
    
            System.out.println("show : 显示栈");
            System.out.println("exit : 退出");
            System.out.println("push : 添加数据");
            System.out.println("pop  : 取出数据");
            System.out.println("请输入:");
            key = scanner.nextLine();
            switch (key){
    
    
                case "show":{
    
    
                    arrayStack.list();
                    break;
                }
                case "exit":{
    
    
                    loop = false;
                    break;
                }
                case "push":{
    
    
                    System.out.print("请输入一个数:");
                    int num = scanner.nextInt();
                    arrayStack.push(num);
                    System.out.println("-----成功啦!!!----------");
                    break;
                }
                case "pop":{
    
    
                    try {
    
    
                        System.out.println("获取数据:"+arrayStack.pop());
                    }catch (Exception e){
    
    
                        System.out.println(e.getMessage());
                    }
                }
                default:break;
            }
        }
    }
// 定义一个类,表示栈结构
class ArrayStack {
    
    

    private int maxSize;
    private int[] stack;
    private int top; // 初始索引

    public ArrayStack(int maxSize) {
    
    
        top = -1;
        this.maxSize = maxSize;
        stack = new int[maxSize];
    }

    // 栈满
    public boolean isFull() {
    
    
        return top == maxSize - 1;
    }

    // 栈空
    public boolean isEmpty() {
    
    
        return top == -1;
    }

    // 添加
    public void push(int num) {
    
    
        if (isFull()) {
    
    
            System.out.println("------栈满-------");
            return;
        }
        top++;
        stack[top] = num;
    }

    // 取出
    public int pop() {
    
    
        if (isEmpty()) {
    
    
            throw new RuntimeException("------栈空啦------");
        }
        return stack[top--];
    }

    // 遍历栈
    public void list(){
    
    
        if(isEmpty()){
    
    
            System.out.println("-----栈空,没有数据------");
            return;
        }

        for (int i = top; i > -1; i--) {
    
    
            System.out.println("stack["+i+"]:"+stack[i]);
        }
    }
}

3)链表模拟栈

class Node {
    
    

    Node next;
    int id;

    public Node(int id, Node next) {
    
    
        this.id = id;
        this.next = next;
    }

    @Override
    public String toString() {
    
    
        return "Node{" +
                "id=" + id +
                '}';
    }
}

class LinkedStack {
    
    

    Node top;
    int maxSize;
    int count;

    public LinkedStack(int maxSize) {
    
    
        top = null;
        this.maxSize = maxSize;
    }

    public boolean isFull() {
    
    
        return count == maxSize;
    }

    public boolean isEmpty() {
    
    
        return count == 0;
    }

    public void push(int id) {
    
    
        if (isFull()) {
    
    
            System.out.println("---栈满了---");
            return;
        }
        top = new Node(id, top);
        count++;
    }

    public Node pop() {
    
    
        if (isEmpty()) {
    
    
            throw new RuntimeException("---栈空----");
        }
        Node node = top;
        top = top.next;
        count--;
        return node;
    }
}

5. 栈实现计算器

1)思路分析

看不懂自己可以尝试画一下,我也没看懂。

在这里插入图片描述

2)代码实现

引用这位同学的,自己想的和老师不太一样,复盘中会给出代码
代码实现 计算器

6. 表达式

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

前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前

举例说明: (3+4)×5-6 对应的前缀表达式就是 - x + 3 4 5 6

前缀表达式的计算机取值

从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果

例如: `(3+4)×5-6` 对应的前缀表达式就是 `- × + 3 4 5 6` , 针对前缀表达式求值步骤如下:

1. **从右至左**扫描,将6、5、4、3压入堆栈
2. 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
3. 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
4. 最后是-运算符,计算出35-6的值,即29,由此得出最终结果

2) 中缀表达式

  1. 中缀表达式就是常见的运算表达式,如(3+4)×5-6
  2. 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式.)

3)后缀表达式

1. 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
2. 举例说明: `(3+4)×5-6` 对应的后缀表达式就是 `3 4 + 5 × 6 –`
3. 比如
正常表达式 逆波兰表达式
a+b a b +
a+(b-c) a b c - +
a+(b-c)*d a b c – d * +
a+d*(b-c) a d b c - * +
a=1+3 a 1 3 + =

后缀表达式的计算机求值

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,
用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;
重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

例如: `(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,由此得出最终结果

7. 逆波兰计算器

public class Test04_逆波兰计算器 {
    
    
    public static void main(String[] args) {
    
    
        // 逆波兰表达式   (3+4)×5-6 --> 3 4 + 5 × 6 –
        // 4 * 5 - 8 + 60 + 8 / 2 --> 4 5 * 8 - 60 + 8 2 / +
        String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
        // 1. 将 表达式放入 arrayList中
        // 2. 将 ArrayList传递给一个方法,遍历ArrayList 配合栈,完成计算
        List<String> listString = getListString(suffixExpression);
        int calculate = calculate(listString);
        System.out.println(calculate);

    }

    // 将一个逆波兰表达式,依次将数据和运算符放到 arrayList中
    public static List<String> getListString(String suffixExpression) {
    
    
        return Arrays.stream(suffixExpression.split(" ")).collect(Collectors.toList());
    }

    // 计算
    public static int calculate(List<String> ls) {
    
    
        // 1. 创建栈
        Stack<String> stack = new Stack<>();
        // 2. 遍历 ls
        ls.stream().forEach(item -> {
    
    
            // 3. 正则表示式取出数
            if (item.matches("\\d+")) {
    
     // 匹配多位数
                // 入栈
                stack.push(item);
            } else {
    
    
                // pop出两个数,并运算,结果入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if (item.equals("+")) {
    
    
                    res = num1 + num2;
                } else if (item.equals("-")) {
    
    
                    res = num1 - num2;
                } else if (item.equals("*")) {
    
    
                    res = num1 * num2;
                } else if (item.equals("/")) {
    
    
                    res = num1 / num2;
                } else {
    
    
                    throw new RuntimeException("符号错误");
                }
                // 把res 入栈
                stack.push(String.valueOf(res));
            }

        });
        // 最后留在stack中的数据就是结果
        return Integer.parseInt(stack.pop());
    }

}

8. 中缀表达式转后缀表达式

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

在这里插入图片描述

public class Test04_逆波兰计算器 {
    
    
    public static void main(String[] args) {
    
    
    /*    // 逆波兰表达式   (3+4)×5-6 --> 3 4 + 5 × 6 –
        // 4 * 5 - 8 + 60 + 8 / 2 --> 4 5 * 8 - 60 + 8 2 / +
        String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
        // 1. 将 表达式放入 arrayList中
        // 2. 将 ArrayList传递给一个方法,遍历ArrayList 配合栈,完成计算
        List<String> listString = getListString(suffixExpression);
        int calculate = calculate(listString);
        System.out.println(calculate);*/

        test1();

    }

    // 中缀表达式转 后缀表达式zs
    // 1. 1 + ( ( 2 + 3 ) * 4 ) -5
    // 2. 先将表达式转为 中缀
    // 3. 中缀转后缀
    public static void test1() {
    
    
        String suffixExpression = "1+((2+3)*4)-5";
        List<String> strings = toInfixExpressionList(suffixExpression);
        System.out.println(strings);
        List<String> strings1 = parseSuffixExpression(strings);
        System.out.println(strings1);
        System.out.println(calculate(strings1));

    }

    // 中缀表达式 --> 转 list
    public static List<String> toInfixExpressionList(String s) {
    
    
        List<String> list = new ArrayList<>();
        int i = 0; // 指针,用于遍历 s
        String str; // 对多位数进行拼接
        char c; // 每遍历一个字符,就放入到 c
        do {
    
    
            // 如果 c是非数字,
            if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
    
    
                list.add("" + c);
                i++;
            } else {
    
    
                // 如果是一个数,需要考虑多位数问题
                str = "";
                while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57) {
    
    
                    str += c;
                    i++;
                }
                list.add(str);
            }
        }
        while (i < s.length());
        return list;
    }

    // list ---> 后缀表达式
    public static List<String> parseSuffixExpression(List<String> list) {
    
    
        // 1. 定义两个栈
        Stack<String> s1 = new Stack<>(); // 符号栈
        // 因为 s2 整个转换过程中 , 没有 pop操作, 后面需要逆序输出, 因此使用 ArrayList替代
//        Stack<String> s2 = new Stack<>(); // 中间结果栈
        List<String> s2 = new ArrayList<>();

        // 2. 遍历 list
        list.stream().forEach(item -> {
    
    
            // 如果是 数 , 进入 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 栈顶的运算符弹出压入 s2 中,
                // 再次与 s1 栈顶的运算符进行比较
                while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){
    
    
                    s2.add(s1.pop());
                }
                // item 压入栈
                s1.push(item);
            }
        });

        // 3. 将 s1 剩余的 运算符加入 s2
        while (s1.size() != 0){
    
    
            s2.add(s1.pop());
        }
        return s2;

    }

    // 将一个逆波兰表达式,依次将数据和运算符放到 arrayList中
    public static List<String> getListString(String suffixExpression) {
    
    
        return Arrays.stream(suffixExpression.split(" ")).collect(Collectors.toList());
    }

    // 计算
    public static int calculate(List<String> ls) {
    
    
        // 1. 创建栈
        Stack<String> stack = new Stack<>();
        // 2. 遍历 ls
        ls.stream().forEach(item -> {
    
    
            // 3. 正则表示式取出数
            if (item.matches("\\d+")) {
    
     // 匹配多位数
                // 入栈
                stack.push(item);
            } else {
    
    
                // pop出两个数,并运算,结果入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if (item.equals("+")) {
    
    
                    res = num1 + num2;
                } else if (item.equals("-")) {
    
    
                    res = num1 - num2;
                } else if (item.equals("*")) {
    
    
                    res = num1 * num2;
                } else if (item.equals("/")) {
    
    
                    res = num1 / num2;
                } else {
    
    
                    throw new RuntimeException("符号错误");
                }
                // 把res 入栈
                stack.push(String.valueOf(res));
            }

        });
        // 最后留在stack中的数据就是结果
        return Integer.parseInt(stack.pop());
    }

}

// 类 ,返回运算符的优先级
class Operation {
    
    
    static int ADD = 1;
    static int SUB = 1;
    static int MUL = 2;
    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:break;
        }
        return result;
    }
}

猜你喜欢

转载自blog.csdn.net/m0_56186460/article/details/124225150
今日推荐