数据结构——栈

概念

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

通俗来讲,大家可以把栈理解为小时候玩手枪上子弹的玩具,先上进去的子弹是在最下面的,第一个射出的子弹是最后一个上进去的子弹,这就是栈与队列不同的地方:先进后出

思路分析

在这里插入图片描述

代码实现

public class ArrayStackDemo {
    public static void main(String[] args) {
        ArrayStack stack = new ArrayStack(3);
        char key = ' ';
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        while (loop) {
            System.out.println("s(show),显示栈");
            System.out.println("a(add),添加数据到栈");
            System.out.println("g(get),从栈取数据");
            System.out.println("e(exit),退出");
            System.out.println("请输入方法:");
            key = scanner.next().charAt(0);
            switch (key){
                case 's' :
                    try {
                        stack.show();
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'a' :
                    System.out.println("请输入一个数:");
                    stack.push(scanner.nextInt());
                    break;
                case 'g' :
                    try {
                        System.out.println("取出数据:" + stack.pop());
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e' :
                    scanner.close();
                    loop = false;
                    break;
            }
        }
        System.out.println("程序结束~~");
    }
}

class ArrayStack{
    private int maxSize;
    private int top;
    private int[] array;

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

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

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

    //入栈
    public void push(int data){
        if (isFull()){
            System.out.println("栈满,无法添加");
            return;
        }
        top++;
        array[top] = data;
    }

    //出栈
    public int pop(){
        if (isEmpty()){
            throw new RuntimeException("栈空,无法读取");
        }
        int value = array[top];
        top--;
        return value;
    }

    //显示栈
    public void show(){
        if (isEmpty()){
            throw new RuntimeException("栈空");
        }
        for (int i = top; i >= 0 ; i--) {
            System.out.printf("array[%d]=%d\n",i,array[i]);
        }
    }
}

简易的综合计算器

需求

输入一个字符串(expression),类似于2+3*8-6,计算出结果,这里的计算器只有+、-、*、/四种。

思路分析

  • 创建两个栈,一个数字栈用于存放数字,一个符号栈用于存放运算符

  • 创建一个指针,从头开始遍历expression,指到指针指向expression的末尾

  • 如果遍历到的是数字

    • 如果该数字是最后一位,直接入栈

    • 如果该数字不是最后一位(这里用于计算多位数,否则只能计算个位数)

      • 如果该数字的下一位是运算符,直接入栈

      • 如果该数字的下一位是数字,记录到临时变量

  • 如果遍历到的是运算符

    • 如果符号栈为空,直接入栈
    • 如果符号栈不为空,判断优先级
      • 如果指针指到的运算符优先级小于等于栈顶的运算符,取出数字栈中的两个数字,取出运算符栈中的一个运算符进行运算,再将运算结果存入数字栈,再将指针指到的运算符存入符号栈
      • 如果指针指到的运算符优先级大于栈顶的运算符,直接入栈
  • 遍历两个栈,取出数字栈中的两个数字,取出运算符栈中的一个运算符进行运算,再将运算结果存入数字栈。遍历结束条件为符号栈为空,最后留在数字栈中的数据就是运算结果

代码实现

首先我们需要先准备一些工具类,比如判断字符是否为符号,判断符号优先级,计算结果,为了方便,统一追加在ArrayStack中

    //判断优先级
    public static int priority(int oper){
        if (oper == '*' || oper == '/'){
            return 1;
        }else if (oper == '+' || oper == '-'){
            return 0;
        }else {
            //暂时只考虑加减乘除
            return -1;
        }
    }

    //判断是否为运算符
    public static boolean isOper(int oper){
        return oper == '*' || oper == '/' || oper == '+' || oper == '-';
    }

    /**
     * 计算结果
     * @param num1 从栈中取出的第一个数
     * @param num2 从栈中取出的第二个数
     * @param oper 运算符
     * @return
     */
    public static int cal(int num1,int num2,int oper){
        //用于存放运算结果
        int result = 0;
        switch (oper){
            case '*' :
                result = num1 * num2;
                break;
            case '/' :
                result = num2 / num1;
                break;
            case '+' :
                result = num1 + num2;
                break;
            case '-' :
                result = num2 - num1;
                break;
        }
        return result;
    }

接下来编写逻辑代码

public class Calculator {
    public static void main(String[] args) {
        String expression = "200+3*7-6";
        //指针,用于遍历表达式
        int index = 0;
        char val = ' ';
        String temp = "";
        ArrayStack numStack = new ArrayStack(10);
        ArrayStack operStack = new ArrayStack(10);
        int num1 = 0;
        int num2 = 0;
        int oper = 0;

        //遍历表达式,将数据存入栈
        while (true){
            val = expression.substring(index, index + 1).charAt(0);
            //如果遍历到的是运算符
            if (ArrayStack.isOper(val)){
                //如果符号栈为空,直接入栈
                if (operStack.isEmpty()){
                    operStack.push(val);
                }else {
                    //如果指针指到的运算符优先级小于等于栈顶的运算符,取出数字栈中的两个数字,取出运算符栈中的一个运算符进行运算,再将运算结果存入数字栈,再将指针指到的运算符存入符号栈
                    if (ArrayStack.priority(val) <= ArrayStack.priority(operStack.showTop())) {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        //计算结果
                        int result = ArrayStack.cal(num1, num2, oper);
                        //将结果存入数字栈,将运算符存入符号栈
                        numStack.push(result);
                        operStack.push(val);
                    } else {
                        //如果指针指到的运算符优先级大于栈顶的运算符,直接入栈
                        operStack.push(val);
                    }
                }
            }else {
                temp = temp + val;
                //如果已经遍历到最后一位,直接入栈
                if (index == expression.length() - 1){
                    numStack.push(Integer.parseInt(temp));
                }else {
                    //如果遍历到的是数字,并且数字的下一位是运算符,直接入栈
                    if (ArrayStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
                        numStack.push(Integer.parseInt(temp));
                        temp = "";
                    }
                }
            }
            index++;
            //如果指针指向表达式末尾,循环结束
            if (index == expression.length()){
                break;
            }
        }

        //计算栈中数据的结果
        while (true){
            //如果符号栈中无数据,计算结束
            if (operStack.isEmpty()){
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            //计算结果
            int result = ArrayStack.cal(num1, num2, oper);
            numStack.push(result);
        }

        System.out.printf("表达式%s=%d",expression,numStack.pop());
    }
}

前缀、中缀、后缀表达式

中缀表达式

中缀表达式是人最能理解的表达式,例如1+2*3-4就是中缀表达式,完全按照生活中的数学逻辑编写,但中缀表达式对于计算机来说,反而是一种非常难理解的表达式,上面简易的综合计算器就是中缀表达式所编写的,我们发现过程非常的麻烦

前缀表达式

前缀表达式是一种没有括号的算术表达式,与中缀表达式不同的是,其将运算符写在前面,操作数写在后面。为纪念其发明者波兰数学家Jan Lukasiewicz,前缀表达式也称为“波兰表达式”。例如,- 1 + 2 3,它等价于1-(2+3)

计算逻辑

  1. 从右往左遍历表达式
  2. 当遍历到的字符为数字时,直接压入栈
  3. 当遍历到的字符为符号时,从栈中取出2个数字进行运算,运算结果重新压入栈
  4. 最后留在栈中的就是表达式的结果

后缀表达式

后缀表达式又被称为“逆波兰表达式”,与前缀表达式完全相反,它是将操作数写在前面,运算符写在后面,不仅便于计算机的理解,对人来说也比前缀表达式理解的要容易一点。例如,1 2 3 + -,它等价于1-(2+3)

计算逻辑

  1. 从左往右遍历
  2. 当遍历到的字符为数字时,直接压入栈
  3. 当遍历到的字符为符号时,从栈中取出2个数字进行运算,运算结果重新压入栈
  4. 最后留在栈中的就是表达式的结果

逆波兰计算器

逆波兰计算器:输入逆波兰表达式,得到结果,计算逻辑咱们已经在上面分析过了,直接上代码

public class BolandCalculator {
    public static void main(String[] args) {
        //逆波兰表达式 (3+4)*5-6
        String expression = "3 4 + 5 * 6 -";
        /*
        思路:
            将字符串分割,存入arraylist
            遍历arraylist计算结果
         */
        List<String> charList = string2List(expression);
        int result = calculator(charList);
        System.out.println(result);
    }

    /**
     * 逆波兰表达式计算器
     * @param list
     * @return
     */
    public static int calculator(List<String> list){
        Stack<String> stack = new Stack<>();
        for (int i = 0; i < list.size(); i++) {
            String str = list.get(i);
            if (str.charAt(0) >= 48 && str.charAt(0) <= 57){
                //当字符为数字时
                stack.push(str);
            }else {
                //当字符为符号时
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int result = 0;
                if (str.equals("+")){
                    result = num1 + num2;
                }else if (str.equals("-")){
                    result = num1 - num2;
                }else if (str.equals("*")){
                    result = num1 * num2;
                }else if (str.equals("/")){
                    result = num1 / num2;
                }else {
                    System.out.println("运算符有误");
                }
                stack.push("" + result);
            }
        }
        return Integer.parseInt(stack.pop());
    }

    /**
     * string转list<string>
     * @param expression 每个字符用空格分开
     * @return
     */
    public static List<String> string2List(String expression){
        String[] split = expression.split(" ");
        List<String> charList = new ArrayList<>();
        for (String c : split){
            charList.add(c);
        }
        return charList;
    }
}

中缀表达式转后缀表达式

后缀表达式计算逻辑已经写完了,并且得到了正确的结果,这并不困难。但是对于人来说,写一个后缀表达式是非常难的,所以我们需要写一个方法来将输入的中缀表达式转换成后缀表达式

思路分析

  1. 准备两个栈,s1用来存放符号,s2用来存放表达式
  2. 从左往右遍历中缀表达式
  3. 当遍历的字符为数字时,直接入s2栈
  4. 当遍历的字符为运算符时
    • s1为空或者s1栈顶运算符为(时,直接入s1栈
    • 当运算符优先级高于s1栈顶运算符的优先级时,直接入s1栈
    • 当运算符优先级不高于s1栈顶运算符的优先级时,取出s1栈顶运算符存入s2栈,再次判断4过程,直到运算符优先级高于s1栈顶运算符的优先级,停止弹出运算符,并且将该运算符压入s1栈
  5. 当遍历的字符为括号时
    • 当字符为(时,直接入s1栈
    • 当字符为)时,取出s1栈顶运算符存入s2栈,重复次过程直到s1栈顶运算符为(时结束,并且丢弃s1栈顶的(
  6. 重复遍历,直到中缀表达式结束
  7. 遍历取出s1栈的运算符存入s2栈
  8. 取出s2栈中的数据,倒过来就是后缀表达式

代码实现

在上述分析中,我们用了两个栈,其实只需要用到s1栈,因为s2栈只用来存数据,在过程中没有取出数据,那么可以使用arraylist代替,这样可以不用进行逆序处理。

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

        String expression = "8+((4+5)*(4-2))-3";
        //1.将字符串转化成arraylist,[(,3,+,4,),*,5,-,6]
        List<String> infixArraylist = infixArraylist(expression);
        System.out.println("中缀表达式为:" + infixArraylist); //中缀表达式为:[(, 3, +, 4, ), *, 5, -, 6]
        //2.中缀表达式转后缀表达式
        List<String> suffixArraylist = infix2suffix(infixArraylist);
        System.out.println("后缀表达式为:" + suffixArraylist); //后缀表达式为:[3, 4, +, 5, *, 6, -]
        int result = calculator(suffixArraylist);
        System.out.printf("表达式%s=%d",expression,result);
    }

    /**
     * 中缀表达式转后缀表达式
     * @param infix
     * @return
     */
    public static List<String> infix2suffix(List<String> infix){
        //用于存放符号的栈
        Stack<String> stack = new Stack<>();
        //用于接受表达式的list
        ArrayList<String> list = new ArrayList<>();
        for (String str : infix){
            if (str.charAt(0) >= 48 && str.charAt(0) <=57){
                //当字符为数字时,直接加入list
                list.add(str);
            }else if (stack.size() == 0 || "(".equals(stack.peek()) || "(".equals(str)){
                //当栈空或者栈顶为左括号或者字符为左括号时,直接入栈
                stack.push(str);
            }else if (")".equals(str)){
                //当字符为右括号时,将栈中的运算符遍历加入到list中,直到遇见左括号
                while (!"(".equals(stack.peek())){
                    list.add(stack.pop());
                }
                stack.pop();
            }else {
                //当字符为符号时,判断优先级
                while (stack.size() != 0 && Operator.priority(stack.peek()) >= Operator.priority(str)){
                    list.add(stack.pop());
                }
                stack.push(str);
            }
        }
        //将栈中遗留的数据存入list
        while (stack.size() > 0){
            list.add(stack.pop());
        }
        return list;
    }

    /**
     * 将中缀表达式转化成中缀表达式的一个list形式
     * @param expression
     * @return
     */
    public static List<String> infixArraylist(String expression){
        int index = 0;
        ArrayList<String> list = new ArrayList<>();
        while (true){
            String substring = expression.substring(index, index + 1);
            list.add(substring);
            index++;
            if (index == expression.length()){
                break;
            }
        }
        return list;
    }

    /**
     * 逆波兰表达式计算器
     * @param list
     * @return
     */
    public static int calculator(List<String> list){
        Stack<String> stack = new Stack<>();
        for (int i = 0; i < list.size(); i++) {
            String str = list.get(i);
            if (str.charAt(0) >= 48 && str.charAt(0) <= 57){
                //当字符为数字时
                stack.push(str);
            }else {
                //当字符为符号时
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int result = 0;
                if (str.equals("+")){
                    result = num1 + num2;
                }else if (str.equals("-")){
                    result = num1 - num2;
                }else if (str.equals("*")){
                    result = num1 * num2;
                }else if (str.equals("/")){
                    result = num1 / num2;
                }else {
                    System.out.println("运算符有误");
                }
                stack.push("" + result);
            }
        }
        return Integer.parseInt(stack.pop());
    }
}

class Operator{
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;

    public static int priority(String oper){
        int result = 0;
        switch (oper){
            case "+" :
                result = ADD;
                break;
            case "-" :
                result = SUB;
                break;
            case "*" :
                result = MUL;
                break;
            case "/" :
                result = DIV;
                break;
        }
        return result;
    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值