1、基本介绍

  • 栈(stack)
  • 栈是一个先入后出(FILO-First In Last Out)的有序列表,出栈(pop),入栈(push)
  • 栈是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)
  • 根据栈的定义可知,最先放入栈的元素在栈底,最后放入的元素在栈顶,而删除元素则刚好相反,最后放入的元素最先删除,最先放入的元素最后删除

  • 栈的应用场景
    • 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完再将地址取出,以回到原来的程序中
    • 处理递归调用:和子程序的调用类似,知识除了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中
    • 表达式的转换和求值
    • 二叉树的遍历
    • 图形的深度优先(depth-frist)搜索法

2、数组模拟栈

image.png

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


import java.util.Scanner;

public class ArrayStackDemo {
    public static void main(String[] args) {
        //测试
        //先创建一个ArrayStack对象->表示栈
        ArrayStack stack = new ArrayStack(4);
        char key = ' ';
        boolean loop = true; //控制是否退出菜单
        Scanner scanner = new Scanner(System.in);

        while(loop) {
            System.out.println("s: 表示显示栈");
            System.out.println("e: 退出程序");
            System.out.println("a: 表示添加数据到栈(入栈)");
            System.out.println("g: 表示从栈取出数据(出栈)");
            System.out.println("请输入你的选择");
            key = scanner.next().charAt(0);
            switch (key) {
                case 's':
                    stack.list();
                    break;
                case 'a':
                    System.out.println("请输入一个数");
                    int value = scanner.nextInt();
                    stack.push(value);
                    break;
                case 'g':
                    try {
                        int res = stack.pop();
                        System.out.printf("出栈的数据是 %d\n", res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e':
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }

        System.out.println("程序退出~~~");

    }
}

//定义ArrayStack类表示栈
class ArrayStack{
    public int maxSize;   //栈的大小
    public int[] stack;   //数组,模拟栈
    public int top = -1;  //栈顶 初始化为 -1

    //构造器
    public ArrayStack(int maxSize){
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }

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

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

    //入栈
    public void push(int value){
        //判断栈是否满
        if(isFull()){
            System.out.println("栈满,无法入栈");
            return;
        }

        //入栈,向将top+1 再赋值
        stack[++top] = value;
    }

    //出栈
    public int pop(){
        //判断栈是否为空
        if(isEmpty()){
            throw new RuntimeException("栈空");
        }
        //出栈,先取值 再将 top -1
        return stack[top--];
    }

    //遍历栈
    public void list(){
        if (isEmpty()){
            System.out.println("栈空");
        }

        for (int i=top ;i>=0;i--){
            System.out.println(stack[i]);
        }
    }
}

3、单链表模拟栈

package stack;

//链表模拟栈
public class LinkedStackDemo {
    public static void main(String[] args) {
        
        //创建栈
        stackList stackList = new stackList();

        //创建节点
        stackNode node01 = new stackNode(1);
        stackNode node02 = new stackNode(2);
        stackNode node03 = new stackNode(3);

        //入栈
        stackList.push(node01);
        stackList.push(node02);
        stackList.push(node03);

        try {
            //出栈
            System.out.println(stackList.pop().num);
            System.out.println(stackList.pop().num);
            System.out.println(stackList.pop().num);
            System.out.println(stackList.pop().num);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }

    }
}

//定义栈节点
class stackNode{
    public int num;
    public stackNode next;

    //构造器
    public stackNode(int num){
        this.num = num;
    }
}


//栈
class stackList{
    //初始化一个头节点
    private stackNode head = new stackNode(0);

    //返回头节点
    public stackNode head(){
        return head;
    }

    //栈空
    public boolean isEmpty(){
        return head.next ==null;
    }

    //入栈
    public void push(stackNode node){
        if (isEmpty()){   //栈空
            head.next = node;
        }else{
            node.next = head.next;
            head.next = node;
        }
    }

    //出栈
    public stackNode pop(){
        if (isEmpty()){   //如果栈空
            throw new RuntimeException("栈空,出栈失败");
        }else {
            stackNode node = head.next;
            head.next=head.next.next;
            return node;
        }
    }

}

4、栈完成表达式计算

  • 计算思路
  1. 通过一个index值(索引),来遍历需要运算的表达式
  2. 如果扫描的是一个数字,就直接入数栈
  3. 如果扫描的是一个字符
    1. 如果符号栈为空,就直接入符号栈
    2. 如果符号栈有操作符,就进行比较,如果当前操作符的优先级小于或等于栈中的操作符,就需要从数栈中pop出两个数,再从符号栈中pop一个符号,进行运算,将得到的结果如数栈,然后将当前的操作符入符号栈,如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
  4. 当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行
  5. 最后在数栈只有一个数字,就是表达式的结果
package Calculator;

//计算表达式的值
public class Calculator{
    public static void main(String[] args) {
        //定义表达式
        String expression = "300+2*6-2";
        //创建数栈
        ArrayStack numStack = new ArrayStack(10);
        //创建符号栈
        ArrayStack opeStack = new ArrayStack(10);

        //定义相关变量
        int index = 0;   //索引
        int num1 = 0;
        int num2 = 0;
        int ope= 0;
        int res = 0;
        char ch = ' '; //保存每次扫描的char值
        String keepNum = ""; //处理多位数用于拼接

        //扫描表达式
        while (true){
            ch = expression.substring(index,index+1).charAt(0);

            if (numStack.isOpe(ch)){   //字符是一个符号
                if (opeStack.isEmpty()){  //字符栈为空直接符号直接入栈
                    opeStack.push(ch);
                }else {   //字符栈不为空,判断该符号和字符栈顶的符号优先级
                    if (opeStack.priority(ch) > opeStack.priority(opeStack.head())){
                        //如果该符号优先级大于栈顶符号优先级,该符号直接入栈
                        opeStack.push(ch);
                    }else {
                        //如果该符号优先级小于等于符号栈顶优先级,则符号栈出栈一个符号,数字栈出栈两个数,并计算
                        //将计算的结果如数栈,将该符号如符号栈
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        ope = opeStack.pop();

                        res = numStack.cal(num1,num2,ope);

                        opeStack.push(ch);
                        numStack.push(res);

                    }
                }
            }else{   //字符是一个数字

                //当处理多位数时,不能发现是一个数就立即入栈
                //需要向expression的表达式的index向后看一位,如果是数则进行扫描,如果是符号则入栈

                //处理多位数
                keepNum += ch;

                //如果ch已经是expression的最后一位,就直接入栈
                if (index == expression.length() -1){
                    numStack.push(Integer.parseInt(keepNum));
                }else{
                    if (opeStack.isOpe(expression.substring(index+1,index+2).charAt(0))){
                        numStack.push(Integer.parseInt(keepNum));
                        //清空keepNum
                        keepNum = "";
                    }
                }

            }

            //让index+1判断是否扫描到expression最后
            index++;
            if (index >= expression.length()){
                break;
            }
        }

        //当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运算
        while (true){
            //如果符号栈为空,则计算完成,数栈中只有一个数值
            if (opeStack.isEmpty()){
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            ope = opeStack.pop();
            res = numStack.cal(num1,num2,ope);

            //将计算出的结果入数栈
            numStack.push(res);
        }

        System.out.println(expression+"="+numStack.pop());

    }
}

//定义栈 数组
class ArrayStack{
    private int maxSize;
    private int top;
    private int[] stack;

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

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

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

    //入栈
    public void push(int num){
        if (isFull()){
            System.out.println("栈满,入栈失败");
            return;
        }else {
            stack[++top] = num;
        }
    }

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

    }

    //判断字符是否是一个符号
    public boolean isOpe(char ch){
        if (ch == '+' || ch == '-' || ch =='*' || ch =='/'){
            return true;
        }else{
            return false;
        }
    }

    //定义符号的优先级  + - * /
    public int priority(int ope){
        if (ope == '/' || ope == '*'){
            return 1;
        }else if (ope == '+' ||ope == '-'){
            return 0;
        }else {
            return -1;
        }
    }

    //获取栈顶的数据
    public int head(){
        return stack[top];
    }

    //计算方法
    public int cal(int num1,int num2,int ope){
        int res =0; // res 用于存放计算结果
        switch (ope){
            case '+':res = num2+num1;
                break;
            case '-':res = num2 - num1;
                break;
            case '*':res = num2 * num1;
                break;
            case '/':res = num2 / num1;
                break;
            default:
                break;
        }

        return res;
    }
}


4、前缀表达式的计算机求值

  • 从右至左扫描表达式,遇到数字时,将数组压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的运算(栈顶元素和次栈顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
  • 例如:(3+4)x 5-6对应的前缀表达式就是 - x + 3 4 5 6,针对前缀表达式求值步骤如下:
    1. 从右至左扫描,将 6 5 4 3压入堆栈
    2. 遇到 + 运算符,弹出 3 4,计算 3 + 4 的值,得 7,再将 7 入栈
    3. 遇到 x 运算符,弹出 7 5 计算 7 x 5 = 35,将 35 入栈
    4. 最后是 - 运算符,计算出 35 - 6 的值,得到 29,由此得到最终结果

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

  • 从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算的出的值即为表达式的结果
  • 例如:(3+4)x 5 - 6对应的后缀表达式为 3 4 + 5 x 6 -,针对后缀表达式求值步骤如下:
    1. 从左到右扫描,将3和4压入堆栈
    2. 遇到 + 运算符,弹出 3 4,计算 3+4的值,得 7,再将7入栈
    3. 将 5 入栈
    4. 遇到 x 运算符,弹出 5 7,计算出 7 x 5=35,将35入栈
    5. 将 6 入栈
    6. 遇到 - 运算符,计算出 35 - 6的值,即 29,由此得出最终结果
package stack;

import java.util.Stack;

public class houzhui {
    public static void main(String[] args) {

        //创建栈
        Stack<String> stack = new Stack<String>();
        //定义后缀表达式
        String expression = "35 4 + 5 * 6 -";

        String[] strings = new String[10];
        strings = expression.split(" ");
        
        int index = 0;
        //遍历list
        while(true){
            String ch = strings[index];
            //如果是符号,则弹出两个数运算,再将结果入栈
            if (ch.equals("+") ||ch.equals("-") || ch.equals("*") || ch.equals("/")){
                int num1 = Integer.parseInt(stack.pop());
                int num2 = Integer.parseInt(stack.pop());
                int res = 0;

                if (ch.equals("+")){
                    res = num2+num1;
                }else if(ch.equals("-")){
                    res = num2-num1;
                }else if(ch.equals("*")){
                    res = num2*num1;
                }else if(ch.equals("/")){
                    res = num2/num1;
                }
                stack.push(""+res);
            }else{
                //如果是数直接入栈
                stack.push(ch);
            }
            if(index < strings.length-1){
                index++;
            }else{
                break;
            }
        }
        System.out.println("计算结果:"+stack.pop());
    }
}

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

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

  • 将中值表达式是 1+((2+3)*4)-5 转中缀表达式
扫描到的元素 S2(栈底->栈顶) S1(栈底->栈顶) 说明
1 1 数字,直接入栈
+ 1 + s1为空,运算符直接入栈
( 1 + ( 左括号,直接入栈
( 1 + ( ( 左括号,直接入栈
2 1 2 + ( ( 数字
+ 1 2 + ( ( + s1栈顶为左括号,运算符直接入栈
3 1 2 3 + ( ( + 数字
) 1 2 3 + + ( 右括号,弹出运算符直至遇到左括号
* 1 2 3 + + ( * s1栈顶为左括号,运算符直接入栈
4 1 2 3 + 4 + ( * 数字
) 1 2 3 + 4 * + 右括号,弹出运算符直至遇到左括号
- 1 2 3 + 4 * + - -与+优先级相同,因此弹出+,再压入-
5 1 2 3 + 4 * + 5 - 数字
到达最右端 1 2 3 + 4 * + 5 - S1中剩余的运算符入栈S2
package stack;

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

public class infixToSuffix {
    public static void main(String[] args) {
        String expression = "1+((2+3)*4)-5";
        //将中缀表达式转成list集合
        List<String> infixExpressionList = toInfixExpressionList(expression);

        //将中缀表达式对应的list 转成后缀表达式对应的list
        List<String> suffixExpressionList = parseSuffixExpressionList(infixExpressionList);
        System.out.println(suffixExpressionList);


    }

    //将String类型的中缀表达式转成List集合
    public static List<String> toInfixExpressionList(String s){
        //定义一个List,存放中中缀表达式
        List<String> ls = new ArrayList<String>();
        int i=0;  //用于遍历中缀表达式字符串
        String str; //对多位数拼接
        char c;  //遍历中缀表达式的字符

        do{
            //如果c是一个非数字,加入到ls
            if ((c = s.charAt(i))<48 || (c=s.charAt(i))>57){
                ls.add(""+c);
                i++;
            }else{
                str = "";  //
                while(i < s.length() && (c = s.charAt(i))>=48 && (c=s.charAt(i))<=57){
                    str += c; //拼接
                    i++;
                }
                ls.add(str);
            }
        }while (i < s.length());
        return ls;
    }

    public static List<String> parseSuffixExpressionList(List<String> list){
        //定义两个栈 符号栈和存储中间结果栈
        Stack<String> s1 = new Stack<String>();  //符号栈
        //因为s2这个栈,在整个转换过程中,没有pop,而且后面还需要逆序输出
        //这里可以直接使用List代替栈s2
        ArrayList<String> s2 = new ArrayList<String>();  //存储中间结果

        //遍历list
        for(String item:list){
            if (item.matches("\\d+")){   //如果是一个数,加入到s2
                s2.add(item);
            }else if(item.equals("(")){   //如果遇到( 直接入栈S1
                s1.push(item);
            }else if(item.equals(")")){   //如果遇到 ),则依次弹出S1栈顶运算符,并入栈S2,直到遇到左括号为止
                while(!s1.peek().equals("(")){
                    s2.add(s1.pop());
                }
                s1.pop();  //将( 弹出s1
            }else {

                //如果s1不为空,并且栈顶运算符优先级高于item优先级
                while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){
                    //将s1栈顶元素弹出加入s2
                    s2.add(s1.pop());
                }

                //将item压入栈
                s1.push(item);
            }
        }
        //将s1中剩余的运算符依次弹出并加入到s2
        while(s1.size() != 0){
            s2.add(s1.pop());
        }

        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.charAt(0)){
            case '+':result = ADD;
                break;
            case '-':result = SUB;
                break;
            case '*':result = MUL;
                break;
            case '/':result = DIV;
                break;
            default:
                System.out.println("不存在该运算符");
                break;
        }
        return result;
    }
}

posted @ 2022-11-06 22:36  youmo~  阅读(59)  评论(0编辑  收藏  举报