什么是栈?Java如何通过栈实现综合计算器?

简介: 什么是栈?Java如何通过栈实现综合计算器?

栈的介绍


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


0a1ad5a182ad462c953312f560dc34a4.png

栈的应用场景

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

栈的快速入门

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


aa1974c9f617432db7183df4c2e6634a.png


实现栈的思路分析

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

代码实现

  1. 创建ArrayStackDomo练习类

67dbd3dd8d3a4c4d9688f0354318747a.png

  1. 定义一个ArrayStack表示栈
  class ArrayStack{
        private int maxSize;  //栈的大小
        private int[] stack;  //数组,数组模拟栈,数据就放在该数组
        private int top = -1; //top表示栈顶,初始化为-1表示没有数据
  }
  1. 编写构造器
   public ArrayStack(int maxSize){
        this.maxSize =maxSize;
        stack = new int[this.maxSize];
  }


  1. 判断栈满
  public boolean isFull(){
       return top == maxSize - 1; //栈顶等于栈的大小的时候表示栈满
    }
  1. 判断栈空
  public boolean isEmpty(){
      return top == -1;
  }
  1. 编写入栈方法 push
        public void push(int value){
            //判断栈是否满
            if (isFull()){
                System.out.println("栈满");
                return;
            }
            top++;
            stack[top] = value;
        }
  1. 编写出栈方法pop,将栈顶数据返回
        public int pop(){
            //先判断栈是否空
            if (isEmpty()){
                //抛出异常
                throw new RuntimeException("栈空,没有数据~");
            }
            int value = stack[top];
            top--;
            return value;
        }
  1. 编写显示栈的情况[遍历栈],便利时,需要从栈顶开始显示数据。
        public void list(){
            if (isEmpty()){
                System.out.println("栈空,没有数据~~");
                return;
            }
            for(int i = top; i >= 0; i--){
                System.out.printf("stack[%d]=%d\n",i,stack[i]);
            }
        }

测试

    public static void main(String[] args) {
        //测试一下ArrayStack是否正确
        //先创建一个ArrayStack对象->表示栈
        ArrayStack stack = 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.print("请输入你的选择:");
            key=scanner.next();
            switch (key){
                case "show":
                    stack.list();
                    break;
                case "exit":
                    scanner.close();
                    loop=false;
                    break;
                case "push":
                    System.out.print("请输入一个数:");
                    int value = scanner.nextInt();
                    stack.push(value);
                    break;
                case "pop":
                    try {
                        int res = stack.pop();
                        System.out.printf("出栈的数据是%d\n",res);
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                default:
                    break;
            }
            System.out.println("---------------------------------");
        }
        System.out.println("程序已退出");
    }
  1. 测试入栈


ed29404f5cba4302a73e621279b3614d.png

  1. 测试显示栈


baef2904cb854b93bcae2ab5cf5e1fee.png

  1. 测试出栈



完整代码

import java.util.Scanner;
/**
 * @author mengzhichao
 * @create 2022-04-15-16:28
 */
public class ArrayStackDemo {
    public static void main(String[] args) {
        //测试一下ArrayStack是否正确
        //先创建一个ArrayStack对象->表示栈
        ArrayStack stack = 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.print("请输入你的选择:");
            key=scanner.next();
            switch (key){
                case "show":
                    stack.list();
                    break;
                case "exit":
                    scanner.close();
                    loop=false;
                    break;
                case "push":
                    System.out.print("请输入一个数:");
                    int value = scanner.nextInt();
                    stack.push(value);
                    break;
                case "pop":
                    try {
                        int res = stack.pop();
                        System.out.printf("出栈的数据是%d\n",res);
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                default:
                    break;
            }
            System.out.println("---------------------------------");
        }
        System.out.println("程序已退出");
    }
}
    //定义一个 ArrayStack 表示栈
    class ArrayStack{
        private int maxSize;  //栈的大小
        private int[] stack;  //数组,数组模拟栈,数据就放在该数组
        private int top = -1; //top表示栈顶,初始化为-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;
        }
        //入栈-push
        public void push(int value){
            //判断栈是否满
            if (isFull()){
                System.out.println("栈满");
                return;
            }
            top++;
            stack[top] = value;
        }
        //出栈-pop,将栈顶的数据返回
        public int pop(){
            //先判断栈是否空
            if (isEmpty()){
                //抛出异常
                throw new RuntimeException("栈空,没有数据~");
            }
            int value = stack[top];
            top--;
            return value;
        }
        //显示栈的情况[便利栈],遍历时,需要从栈顶开始显示数据
        public void list(){
            if (isEmpty()){
                System.out.println("栈空,没有数据~~");
                return;
            }
            for(int i = top; i >= 0; i--){
                System.out.printf("stack[%d]=%d\n",i,stack[i]);
            }
        }
    }


栈的一个实际需求

假如我们输入一串表达式字符串。计算机底层是如何运算得到结果的?如果用栈实现呢?


7+264


使用栈完成表达式的计算思路

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

完整代码实现

/**
 * @author mengzhichao
 * @create 2022-04-16-11:12
 */
public class Calculator {
    public static void main(String[] args) {
        //定义一个表达式
        String expression = "7+2*6-4";
        //创建两个栈,一个数栈,一个符号栈
        CalculatorArrayStack numStack = new CalculatorArrayStack(100);
        CalculatorArrayStack operStack = new CalculatorArrayStack(100);
        int index = 0; //用于扫描
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch =' '; //将每次扫描得到的char保存在ch中
        //开始while循环的扫描 expression
        while (true){
            //一次得到 experssion 的每一个字符
            ch = expression.substring(index,index+1).charAt(0);
            //判断ch是什么,然后做相应的处理
            if (operStack.isOper(ch)){ //如果是运算符
                //判断当前的符号栈是否为空
                if (!operStack.isEmpty()){
                    //如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,
                    //就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,
                    //进行运算,将得到的结果,入数栈,然后将当前的操作符入符号栈
                    if (operStack.priority(ch) <= operStack.priority(operStack.peek())){
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        res = numStack.cal(num1,num2,oper);
                        //把运算的结果入数栈
                        numStack.push(res);
                        //然后将当前的操作符入符号栈
                        operStack.push(ch);
                    }else {
                        //如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
                        operStack.push(ch);
                    }
                }else {
                    //如果为空直接入符号栈...
                    operStack.push(ch); // 7 * 2
                }
            }else {
                numStack.push(ch - 48);
            }
            //让index + 1,并判断是否扫描到expression最后
            index++;
            if (index >= expression.length()){
                break;
            }
        }
        //当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并进行运算。
        while (true){
            //如果符号栈为空,则计算到最后的结果,数栈中只有一个数字【结果】
            if (operStack.isEmpty()){
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.cal(num1,num2,oper);
            numStack.push(res);
        }
        //将数栈的最后数,pop出,就是结果
        int res2 = numStack.pop();
        System.out.printf("表达式%s = %d",expression,res2);
    }
}
//先创建一个栈
class CalculatorArrayStack{
    private int maxSize;  //栈的大小
    private int[] stack;  //数组,数组模拟栈,数据就放在该数组
    private int top = -1; //top表示栈顶,初始化为-1表示没有数据
    //构造器
    public CalculatorArrayStack(int maxSize){
        this.maxSize =maxSize;
        stack = new int[this.maxSize];
    }
    //栈满
    public boolean isFull(){
        return top == maxSize - 1; //栈顶等于栈的大小的时候表示栈满
    }
    //栈空
    public boolean isEmpty(){
        return top == -1;
    }
    //入栈-push
    public void push(int value){
        //判断栈是否满
        if (isFull()){
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = value;
    }
    //返回栈顶的值,不做任何修改
    public int peek(){
        return stack[top];
    }
    //出栈-pop,将栈顶的数据返回
    public int pop(){
        //先判断栈是否空
        if (isEmpty()){
            //抛出异常
            throw new RuntimeException("栈空,没有数据~");
        }
        int value = stack[top];
        top--;
        return value;
    }
    //显示栈的情况[便利栈],遍历时,需要从栈顶开始显示数据
    public void list(){
        if (isEmpty()){
            System.out.println("栈空,没有数据~~");
            return;
        }
        for(int i = top; i >= 0; i--){
            System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }
    //返回运算符的优先级,优先级是程序员来确定,优先级使用数字表示
    //数字越大,则优先级越高
    public int priority(int oper){
        if (oper == '*' || oper == '/'){
            return 1;
        }else if (oper == '+' || oper == '-'){
            return 0;
        } else {
            return -1; //假定目前的表达式只有+,-,*,/
        }
    }
    //判断是不是一个运算符
    public boolean isOper(char val){
        return val == '+' || val == '-' || val == '*' || val == '/';
    }
    //计算方法
    public int cal(int num1,int num2,int oper){
        int res = 0; //res 用于存放计算的结果
        switch (oper){
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1; //注意顺序
                break;
            case '*':
                res = num1 * num2;
                break;
            case '/':
                res = num2 / num1;
                break;
            default:
                break;
        }
        return res; //结果返回
    }
}

运行并测试

5d49aa170b354d5dbbcc06359c52209d.png

经过测试我们发现通过程序计算出来的结果和我们算出来的结果是一致的,但是不知道大家有没有发现一个问题当我们把表达式中的 7 改为 70 后我们的计算结果就会出现一些问题。

8142c5e4f0804edd98f175c153b3eb4e.png

那要怎么解决这个问题呢?我们接着往下看!

功能优化思路分析以及代码完善

  1. 当处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
  2. 在处理数,需要向expression的表达式的index 后再看一位,如果是数就进行扫描,如果是符号才入栈
  3. 因此我们需要定义一个变量 字符串,用于拼接
/**
 * @author mengzhichao
 * @create 2022-04-16-11:12
 */
public class Calculator {
    public static void main(String[] args) {
        //定义一个表达式
        String expression = "7*2*2-5+1-5+3-4";
        //创建两个栈,一个数栈,一个符号栈
        CalculatorArrayStack numStack = new CalculatorArrayStack(100);
        CalculatorArrayStack operStack = new CalculatorArrayStack(100);
        int index = 0; //用于扫描
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch =' '; //将每次扫描得到的char保存在ch中
        String keepNum = ""; //用于拼接多位数
        //开始while循环的扫描 expression
        while (true){
            //一次得到 experssion 的每一个字符
            ch = expression.substring(index,index+1).charAt(0);
            //判断ch是什么,然后做相应的处理
            if (operStack.isOper(ch)){ //如果是运算符
                //判断当前的符号栈是否为空
                if (!operStack.isEmpty()){
                    //如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,
                    //就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,
                    //进行运算,将得到的结果,入数栈,然后将当前的操作符入符号栈
                    if (operStack.priority(ch) <= operStack.priority(operStack.peek())){
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        res = numStack.cal(num1,num2,oper);
                        //把运算的结果入数栈
                        numStack.push(res);
                        //然后将当前的操作符入符号栈
                        operStack.push(ch);
                    }else {
                        //如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
                        operStack.push(ch);
                    }
                }else {
                    //如果为空直接入符号栈...
                    operStack.push(ch); // 7 * 2
                }
            }else {
//                numStack.push(ch - 48);
                //分析思路
                //1. 当处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
                //2. 在处理数,需要向expression的表达式的index 后再看一位,如果是数就进行扫描,如果是符号才入栈
                //3. 因此我们需要定义一个变量 字符串,用于拼接
                //处理多位数
                keepNum += ch;
                //如果ch已经是expression的最后一位,就直接入栈。
                if (index == expression.length() - 1){
                    numStack.push(Integer.parseInt(keepNum));
                }else {
                    //判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入栈
                    //注意是看后一位,不是index++
                    if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
                        //如果后一位是运算符,则入栈 keepNum = "1" 或者 "123"
                        numStack.push(Integer.parseInt(keepNum));
                        //重要的!!!!!!,keepNum清空
                        keepNum = "";
                    }
                }
            }
            //让index + 1,并判断是否扫描到expression最后
            index++;
            if (index >= expression.length()){
                break;
            }
        }
        //当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并进行运算。
        while (true){
            //如果符号栈为空,则计算到最后的结果,数栈中只有一个数字【结果】
            if (operStack.isEmpty()){
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.cal(num1,num2,oper);
            numStack.push(res);
        }
        //将数栈的最后数,pop出,就是结果
        int res2 = numStack.pop();
        System.out.printf("表达式%s = %d",expression,res2);
    }
}
//先创建一个栈
class CalculatorArrayStack{
    private int maxSize;  //栈的大小
    private int[] stack;  //数组,数组模拟栈,数据就放在该数组
    private int top = -1; //top表示栈顶,初始化为-1表示没有数据
    //构造器
    public CalculatorArrayStack(int maxSize){
        this.maxSize =maxSize;
        stack = new int[this.maxSize];
    }
    //栈满
    public boolean isFull(){
        return top == maxSize - 1; //栈顶等于栈的大小的时候表示栈满
    }
    //栈空
    public boolean isEmpty(){
        return top == -1;
    }
    //入栈-push
    public void push(int value){
        //判断栈是否满
        if (isFull()){
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = value;
    }
    //返回栈顶的值,不做任何修改
    public int peek(){
        return stack[top];
    }
    //出栈-pop,将栈顶的数据返回
    public int pop(){
        //先判断栈是否空
        if (isEmpty()){
            //抛出异常
            throw new RuntimeException("栈空,没有数据~");
        }
        int value = stack[top];
        top--;
        return value;
    }
    //显示栈的情况[便利栈],遍历时,需要从栈顶开始显示数据
    public void list(){
        if (isEmpty()){
            System.out.println("栈空,没有数据~~");
            return;
        }
        for(int i = top; i >= 0; i--){
            System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }
    //返回运算符的优先级,优先级是程序员来确定,优先级使用数字表示
    //数字越大,则优先级越高
    public int priority(int oper){
        if (oper == '*' || oper == '/'){
            return 1;
        }else if (oper == '+' || oper == '-'){
            return 0;
        } else {
            return -1; //假定目前的表达式只有+,-,*,/
        }
    }
    //判断是不是一个运算符
    public boolean isOper(char val){
        return val == '+' || val == '-' || val == '*' || val == '/';
    }
    //计算方法
    public int cal(int num1,int num2,int oper){
        int res = 0; //res 用于存放计算的结果
        switch (oper){
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1; //注意顺序
                break;
            case '*':
                res = num1 * num2;
                break;
            case '/':
                res = num2 / num1;
                break;
            default:
                break;
        }
        return res; //结果返回
    }
}

再次测试

2e5b942f3a254b4e891708c076934d5e.png

该问题成功解决。


相关文章
|
3月前
|
存储 算法 Java
惊!Java程序员必看:JVM调优揭秘,堆溢出、栈溢出如何巧妙化解?
【8月更文挑战第29天】在Java领域,JVM是代码运行的基础,但需适当调优以发挥最佳性能。本文探讨了JVM中常见的堆溢出和栈溢出问题及其解决方法。堆溢出发生在堆空间不足时,可通过增加堆空间、优化代码及释放对象解决;栈溢出则因递归调用过深或线程过多引起,调整栈大小、优化算法和使用线程池可有效应对。通过合理配置和调优JVM,可确保Java应用稳定高效运行。
135 4
|
9天前
|
存储 算法 Java
🧠Java零基础 - Java栈(Stack)详解
【10月更文挑战第17天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 2
|
26天前
|
存储 安全 Java
【用Java学习数据结构系列】探索栈和队列的无尽秘密
【用Java学习数据结构系列】探索栈和队列的无尽秘密
28 2
|
3月前
|
Java 索引
java中的栈(利用数组实现栈)
这篇文章通过Java代码示例介绍了如何使用数组实现栈操作,包括栈的初始化、入栈、出栈、判断栈满和空以及遍历栈的方法。
java中的栈(利用数组实现栈)
|
4月前
|
Java 运维
开发与运维命令问题之使用jstack命令查看Java进程的线程栈如何解决
开发与运维命令问题之使用jstack命令查看Java进程的线程栈如何解决
62 2
|
4月前
|
存储 安全 Java
Java面试题:在JVM中,堆和栈有什么区别?请详细解释说明,要深入到底层知识
Java面试题:在JVM中,堆和栈有什么区别?请详细解释说明,要深入到底层知识
78 3
|
4月前
|
存储 缓存 监控
Java面试题:解释堆和栈的OutOfMemoryError通常在什么情况下会发生
Java面试题:解释堆和栈的OutOfMemoryError通常在什么情况下会发生
46 3
|
4月前
|
存储 Java 对象存储
Java虚拟机(JVM)中的栈(Stack)和堆(Heap)
在Java虚拟机(JVM)中,栈(Stack)和堆(Heap)是存储数据的两个关键区域。它们在内存管理中扮演着非常重要的角色,但各自的用途和特点有所不同。
48 0
|
4月前
|
存储 Java
JAVA程序运行问题之JVM 中的栈如何解决
JAVA程序运行问题之JVM 中的栈如何解决
|
4月前
|
存储 安全 Java
Java集合篇之逐渐被遗忘的Stack,手写一个栈你会吗?
总之,虽然在日常开发中,`java.util.Stack`正逐渐被其他类如 `Deque`接口的实现所取代,但手写一个栈(无论是基于数组还是链表)都是一次很好的编程练习,它可以帮助开发者更加深入地理解栈这种数据结构的工作原理和各种操作。
34 0