代码随笔-队列与栈

2. 队列

  • 应用场景

银行排队区号,先到先得,先进先出。。。

基本介绍

  • 队列是一个有序列表,可以用数组或是链表来实现。【任何线性数据结构都有两种存储结构来实现。】

  • 队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

  • 遵循 先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出。

数组模拟队列

  • 队列本身是有序列表,若使用数组的结构来存储队列的数据,而队列数组的声明如原理图所示,MaxSize 是该队列的最大容量。

  • 因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 frontrear 分别记录队列前后端的下标,front 会随着数据输出而改变,而 rear 则是随着数据输入而改变。

  • 当我们将数据存入队列时称为 “ addQueue ”, addQueue 的处理需要有两个步骤:思路分析

  1. 将尾指针往后移:rear + 1,当 front == rear【空】

  1. 若尾指针rear 小于队列的最下标 maxSize-1,则将数据存入 rear 所指的数组元素中,否则无法存入数据。 rear == maxSize-1 【队列满】

代码实现

import java.util.Scanner;

/**
 * 数组模拟队列
 */
public class ArrayQueueDemo {
    public static void main(String[] args) {

        //测试
        //创建一个队列
        ArrayQueue arrayQueue = new ArrayQueue(3);
        char key = ' ';//接收用户输入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        //输出一个菜单
        while(loop){
            System.out.println("s(show):显示队列");
            System.out.println("e(exit):退出队列");
            System.out.println("a(add) :添加数据到队列");
            System.out.println("g(get) :从队列取出数据");
            System.out.println("h(head):查看队列头的数据");
            key = scanner.next().toLowerCase().charAt(0);//接收一个字符
            switch (key){
                case 's':
                    arrayQueue.listQueue();
                    break;
                case 'e':
                    scanner.close();
                    loop = false;
                    break;
                case 'a':
                    System.out.println("请输入你要添加的数据:");
                    int n = scanner.nextInt();
                    arrayQueue.addQueue(n);
                    break;
                case 'g':
                    try {
                        int res = arrayQueue.getQueue();
                        System.out.printf("取出的数据是%d\n",res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        int i = arrayQueue.headQueue();
                        System.out.printf("队列头数据是%d\n",i);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
            }
            System.out.println("程序退出~");
        }

    }
}
//编写一个 ArrayQueue 类
class ArrayQueue{
    private int maxSize;//表示数组的最大容量
    private int front;//队列头
    private int rear;//队列尾
    private int[] arr;//该数组用于存放数据,模拟队列

    //创建队列的构造器
    public ArrayQueue(int maxSize){
        this.maxSize = maxSize;
        arr = new int[maxSize];
        front = -1;//指向队列头部,分析出 front 是指向队列头前一个位置。此时没有数据。
        rear = -1;//指向队列尾部,指向队列尾的数据(即就是队列最后一个数据)
    }

    //判断队列是否满
    public boolean isFull(){
        return rear == maxSize-1;
    }

    //判断队列是否为空
    public boolean isEmpty(){
        return rear == front;
    }

    //添加数据到队列
    public void addQueue(int data){
        //判断队列是否满
        if(isFull()){
            System.out.println("队列满,不能加入数据");
            return;
        }
        rear++;//让 rear 后移
        arr[rear] = data;
    }

    //获取队列的数据,出队列
    public int getQueue(){
        //判断队列是否为空
        if(isEmpty()){
            //通过抛出异常来处理
            throw new RuntimeException("队列为空,不能取数据");
        }
        front++;//front 后移
        return arr[front];
    }

    //显示队列的所有数据
    public void listQueue(){
        //遍历
        if(isEmpty()){
            System.out.println("队列空,没有数据");
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.printf("arr[%d]=%d\n",i,arr[i]);
        }
    }

    //显示队列的头数据,注意:不是取出数据【使用 front+1 】
    public int headQueue(){
        //判断
        if(isEmpty()){
            throw new RuntimeException("队列空,没有数据");
        }
        //注意,这里不能使用 ++front,因为一旦改变了front值,就回不去了
        return arr[front+1];
    }
}
  • 问题分拟

  1. 目前数组使用一次就不能用,没有达到复用的效果。

  1. 将这个数组使用算法,改进成一个环形的数组 %(用到取模)

数组模拟环形队列(循环队列)

  • 对前面的数组模拟队列进行优化,充分利用数组。因此将数组看做一个环形的。(通过取模的方式)

思路分析

  1. 为了避免当只有一个元素时,对头和队尾重合使处理变得麻烦,现调整 front 和 rear 变量(指针)的含义:

  1. front:指向队列的第一个元素,也就是说 arr[front] 就是队列的第一个元素。front 的初始值 = 0;

  1. rear:指向队列的最后一个元素的下一个位置。rear 的初始值 = 0;希望空出一个空间作为约定。【此时当 front 等于 rear 时,此队列就没有剩下元素,而是空队列】

  1. 如何表示队列满了的条件:当数组全部被填满时,此时 front = rear,该条件与空队列条件重合,因此无法判断!

  1. 方法一:设置一个标志变量 flag,当 front == rear,且 flag=0 时,队列为空;当 front == rear,且 flag=1 时,队列为满。

  1. 方法二:当队列为空时,条件就是 front == rear,当队列满时,我们修改其条件,保留一个元素空间。也就是说,队列满时,数组中还有一个空闲单元。【约定】

所以尽管 rear 和 front 只相差一个位置时就是满的情况,但也可能是相差整整一圈。所以若队列的最大尺寸为 maxSize,那么队列满的条件时:(rear+1)%maxSize == front (取模%的目的就是为了整合 rear 和 front 大小为一个问题)

  1. 队列中有效的数据的个数(通用计算队列长度公式):(rear - front + maxSize)% maxSize

代码实现

import java.util.Scanner;

/**
 * 数组模拟循环队列
 */
public class CircleArrayQueueDemo {
    public static void main(String[] args) {

        //测试
        CircleArray circleArray = new CircleArray(4);
        char key = ' '; // 接收用户的输入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        //输出一个菜单
        while(loop){
            System.out.println("s(show):显示队列");
            System.out.println("e(exit):退出队列");
            System.out.println("a(add) :添加数据到队列");
            System.out.println("g(get) :从队列取出数据");
            System.out.println("h(head):查看队列头的数据");
            key = scanner.next().toLowerCase().charAt(0);//接收一个字符
            switch (key){
                case 's':
                    circleArray.listQueue();
                    break;
                case 'e':
                    scanner.close();
                    loop = false;
                    break;
                case 'a':
                    System.out.println("请输入你要添加的数据:");
                    int n = scanner.nextInt();
                    circleArray.addQueue(n);
                    break;
                case 'g':
                    try {
                        int res = circleArray.getQueue();
                        System.out.printf("取出的数据是%d\n",res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        int i = circleArray.headQueue();
                        System.out.printf("队列头数据是%d\n",i);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
            }
        }
        System.out.println("程序退出~");
    }
}
class CircleArray{
    private int maxSize;//表示数组的最大容量
    //front:指向队列的第一个元素,也就是说 arr[front] 就是队列的第一个元素。
    // front 的初始值 = 0;
    private int front;//队列头
    //rear:指向队列的最后一个元素的下一个位置。rear 的初始值 = 0;
    private int rear;//队列尾
    private int[] arr;//该数组用于存放数据,模拟队列

    //创建队列的构造器
    public CircleArray(int maxSize){
        this.maxSize = maxSize;
        arr = new int[maxSize];
        front = 0;//因为默认值就是0,因此可以不写此句
        rear = 0;//因为默认值就是0,因此可以不写此句
    }

    //判断队列是否满
    public boolean isFull(){
        return (rear + 1) % maxSize == front;
    }

    //判断队列是否为空
    public boolean isEmpty(){
        return rear == front;
    }

    //添加数据到队列
    public void addQueue(int data){
        //判断队列是否满
        if(isFull()){
            System.out.println("队列满,不能加入数据");
            return;
        }
        //直接将数据加入
        arr[rear] = data;
        //将 rear 后移,这里必须考虑取模
        rear = (rear + 1) % maxSize;
    }

    //获取队列的数据,出队列
    public int getQueue(){
        //判断队列是否为空
        if(isEmpty()){
            //通过抛出异常来处理
            throw new RuntimeException("队列为空,不能取数据");
        }
        //这里需要分析出 front 是指向队列的第一个元素
        //1.先把 front 对应的值保存到一个临时变量
        //2.将 front 后移
        //3.将临时保存的变量返回
        int value = arr[front];
        front = (front + 1) % maxSize;
        return value;
    }

    //显示队列的所有数据
    public void listQueue(){
        //遍历
        if(isEmpty()){
            System.out.println("队列空,没有数据");
            return;
        }
        // 思路:从 front 开始遍历,遍历多少个元素。
        // 需要知道有多少个元素,因此写个方法 size() 获得元素的个数
        for (int i = front; i < front + size(); i++) {
            System.out.printf("arr[%d]=%d\n",i % maxSize,arr[i % maxSize]);
        }
    }

    //求出当前队列有效数据的个数
    public int size(){
        return (rear - front + maxSize) % maxSize;
    }

    //显示队列的头数据
    public int headQueue(){
        //判断
        if(isEmpty()){
            throw new RuntimeException("队列空,没有数据");
        }
        return arr[front];
    }

}

3. 栈

基本介绍

  1. 栈的定义:是限定仅在表尾进行插入和删除操作的线性表。这里说的表尾指的是 栈顶。

  1. 允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又被称为 先进后出的线性表。

  1. 栈的插入操作叫做进栈,也称为压栈(push)、入栈。 栈的删除操作叫做出栈,也叫弹栈(pop)。

  1. 栈的应用场景:

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

  1. 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入到堆栈中。【编译器使用栈实现了递归】【在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态】。

  1. 表达式的转换【中缀表达式转后缀表达式】与求值

  1. 二叉树的遍历

  1. 图形的深度优先搜索法。

顺序栈

  • 栈的顺序存储结构 ——数组模拟

  • 选择下标为 0 的一端作为栈底,因为首元素变化量最小

  • 实现顺序栈的思路模拟:

  1. 使用数组来模拟栈,定义储栈的长度 StackSize;

  1. 定义一个 top 变量来表示栈顶,初始化为 -1。因此空栈的判定条件定位 top == -1;

  1. 入栈的操作:当有数据加入到栈时,top++;stack[ top ] = data;

  1. 出栈的操作:int val = stackl[ top ];top - -;return val;

/**
 * 数组模拟栈
 */
public class ArrayStackDemo {
    public static void main(String[] args) {
        //测试 ArrayStack 是否正确
        //先创建一个 ArrayStack 对象 表示栈
        ArrayStack stack = new ArrayStack(4);
        stack.push(1);
        stack.push(14);
        stack.push(53);
        stack.push(54);
        stack.list();
        stack.pop();
        stack.pop();
        stack.pop();
        stack.list();
    }
}
//定义一个 栈类
class ArrayStack{
    private int stackSize;//栈的大小
    private int[] stack;//数组,数组模拟栈,数据就放在该数组
    private int top = -1;//top 表示栈顶

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

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

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

    //入栈
    public void push(int val){
        //先判断 栈是否满
        if(isFull()){
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = val;
    }

    //出栈,将栈顶的数据返回
    public int pop(){
        //先判断栈是否空
        if(isEmpty()){
            //抛出异常
            throw new RuntimeException("栈空");
        }
        int val = stack[top];
        top--;
        return val;
    }

    //显示栈的情况[遍历栈],遍历时需要从栈顶开始显示数据
    public void list(){
        if(isEmpty()){
            System.out.println("栈空");
            return;
        }
        for (int i = top; i > -1; i--) {
            System.out.printf("stack[%d] = %d\n",i,stack[i]);
        }
    }

}

链栈

  • 栈的链式存储结构 --> 链栈

  • 链表实现栈结构时,一般选择链表的头部作为栈顶,因此单链表的头结点就可以不需要了。通常对于链栈来说,是不需要头结点的。

  • 实现链栈的思路模拟:

  • 空栈:top == null;

  • 链表一般不存在栈满的情况。

  • 进栈:newNode.next = top; top = newNode;

  • 出栈:int val = top.val ; top = top.next;

/**
 * @author 张玉龙
 * 我亦无他,惟手熟尔~
 * 追求卓越,越努力越幸运!
 */
public class SingleLinkedListStackDemo {
    public static void main(String[] args) {

        SingleLinkedListStack singleLinkedListStack = new SingleLinkedListStack();
        singleLinkedListStack.push(10);
        singleLinkedListStack.push(9);
        singleLinkedListStack.push(8);
        singleLinkedListStack.push(7);
        singleLinkedListStack.push(6);
        singleLinkedListStack.list();
        System.out.println("弹出的值");
        System.out.println(singleLinkedListStack.pop());
        System.out.println(singleLinkedListStack.pop());
        System.out.println(singleLinkedListStack.pop());
        System.out.println("剩余");
        singleLinkedListStack.list();
    }
}
class SingleLinkedListStack{
    StackNode top;

    public SingleLinkedListStack() {
    }

    //进栈
    public void push(int data){
        StackNode node = new StackNode(data);
        if(top == null){
            top = node;
            return;
        }else{
            node.next = top;
            top = node;
        }
    }

    //出栈
    public int pop(){
        if(top == null){
            throw new RuntimeException("空栈");
        }
        StackNode temp = top.next;//temp 保留下一个结点
        int val = top.data;
        top.next = null;//释放 top
        top = temp;
        return val;
    }

    //遍历栈
    public void list(){
        if(top == null){
            System.out.println("空栈");
        }
        StackNode temp = top;
        while(temp != null){
            System.out.println(temp);
            temp = temp.next;
        }
    }

}
class StackNode{
    public int data;//存放数据
    public StackNode next;//指针域

    public StackNode(int data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "StackNode{" +
                "data=" + data +
                '}';
    }
}

栈的应用

使用栈实现计算器

  • 思路:

  1. 通过一个 index 值(索引),来遍历我们的表达式

  1. 如果我们发现是一个数字,就直接入数栈

  1. 如果发现扫描到的是一个符号,就分如下情况:

  1. 如果发现当前的符号栈为空,就直接入栈

  1. 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中 pop 出两个数,在从符号栈中 pop 出一个符号,进行运算,将得到的结构入数栈,然后将当前的操作符入符号栈。如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈。

  1. 当表达式扫描完毕,就顺序的从数栈和 符号栈中 pop 出相应的数和符号,并运行。

  1. 最后在数栈只有一个数字,就是表达式的结果。

/**
 * 栈的应用——模拟计算器(中缀表达式)
 */
public class Calculator {
    public static void main(String[] args) {

        //根据前面的思路,完成表达式的运算
        String expression = "78+2*3-9";
        //创建两个栈,数栈、符号栈
        ArrayStack2 numStack = new ArrayStack2(10);
        ArrayStack2 operStack = new ArrayStack2(10);
        //定义需要的相关变量
        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){
            //依次得到 expression 的每一个字符
            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);
                }

            }else{//如果是数,则直接入数栈
//                numStack.push(ch);// 错误的方式,因为 '1' 字符1  不是数字 1!!!
//                numStack.push(ch - 48);//根据 ASC 码值  也存在问题,不能处理多位数
                //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
                        numStack.push(Integer.parseInt(keepNum));
                        // 注意:要清空 keepNum
                        keepNum = "";
                    }
                }

            }
            //让 index +1,并判断是否扫描到 expression 最后。
            index++;
            if(index >= expression.length()){
                break;
            }
        }
        //当表达式扫描完毕,就顺序的从数栈和 符号栈中 pop 出相应的数和符号,并运行
        while(true){
            //如果符号栈为空,则计算到最后的结果,数栈中只有一个数字,即结果
            if(operStack.isEmpty()){
                break;
            }else{
                num1 = numStack.pop();
                num2 = numStack.pop();
                oper = operStack.pop();
                res = numStack.cal(num1,num2,oper);
                numStack.push(res);//入栈
            }
        }
        //将数栈的最后数,pop 出,就是结果
        System.out.printf("表达式%s = %d",expression,numStack.pop());
    }
}
//先创建一个栈
// 定义一个 ArrayStack2 的栈,需要扩展功能
class ArrayStack2{
    private int stackSize;//栈的大小
    private int[] stack;//数组,数组模拟栈,数据就放在该数组
    private int top = -1;//top 表示栈顶

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

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

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

    //入栈
    public void push(int val){
        //先判断 栈是否满
        if(isFull()){
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = val;
    }

    //出栈,将栈顶的数据返回
    public int pop(){
        //先判断栈是否空
        if(isEmpty()){
            //抛出异常
            throw new RuntimeException("栈空");
        }
        int val = stack[top];
        top--;
        return val;
    }

    //显示栈的情况[遍历栈],遍历时需要从栈顶开始显示数据
    public void list(){
        if(isEmpty()){
            System.out.println("栈空");
            return;
        }
        for (int i = top; i > -1; 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;// 用于存放计算的结果
        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;
    }

    //增加一个方法,可以返回当前栈顶的值,但是不是真正的 pop
    public int peek(){
        return stack[top];
    }
}

前缀、中缀、后缀表达式

前缀表达式

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

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

  1. 前缀表达式的求值流程:

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

中缀表达式

  1. 中缀表达式就是常见的运算表达式

  1. 中缀表达式的求值是我们最熟悉的,但是对计算机来说却不好操作,因此在计算结果时,往往会将中缀表达式转成其他表达式来操作(一般转成后缀表达式。)

后缀表达式

  1. 后缀表达式又称为逆波兰表达式。只是运算符位于操作数之后

  1. 后缀表达式的求值流程:

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

逆波兰计算器

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

/**
 * 后缀表达式计算结果
 */
public class PolandNotation {
    public static void main(String[] args) {
        //先定义逆波兰表达式
        //(3+4)×5-6
        //为了方便,逆波兰表达式的数字和符号使用空格隔开
        String suffixExpression = "30 4 + 5 * 6 -";
        //思路:
        //1. 先将"3 4 + 5 × 6 -" ==> 放到 ArrayList 中
        //2. 将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈完成计算
        List<String> rpnList = getListString(suffixExpression);
        System.out.println(rpnList);

        int res = calculate(rpnList);
        System.out.println(res);

    }
    public static List<String> getListString(String suffixExpression){
        String[] split = suffixExpression.split(" ");
        List<String> list = new ArrayList<>();
        for (String ele : split) {
            list.add(ele);
        }
        return list;
    }

    //完成对 逆波兰表达式的运算
    public static int calculate(List<String> ls){
        //创建栈,只需要一个栈即可
        Stack<String> stack = new Stack<>();
        //遍历 ls
        for (String item : ls) {
            //这里使用正则表达式来读取数
            if(item.matches("\\d+")){//匹配的是多位数
                //入栈
                stack.push(item);
            }else{
                //pop 出两个数,并运算,再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                switch(item){
                    case "+":
                        res = num1 + num2;
                        break;
                    case "-":
                        res = num1 - num2;
                        break;
                    case "*":
                        res = num1 * num2;
                        break;
                    case "/":
                        res = num1 / num2;
                        break;
                    default:
                        throw new RuntimeException("运算符有误");
                }
                //把 res 入栈
                stack.push(res+"");
            }
        }
        //最后留在 stack 中的数据就是运算结果
        return Integer.parseInt(stack.pop());
    }

}

中缀表达式转后缀表达式

  • 具体步骤

  1. 初始化两个栈:运算符栈s1 和存储中间结果的栈 s2;

  1. 从左至右扫描中缀表达式;

  1. 遇到操作数时,将其压入 s2

  1. 遇到运算符时,比较其与s1栈顶运算符的优先级:

  1. 如果 s1 为空,或栈顶运算符为左括号“( ”,则直接将此运算符入栈;

  1. 否则,若优先级比栈顶运算符的高,也将运算符压入 s1;

  1. 否则,将s1栈顶的运算符弹出并压入到 s2中,再次转到(4-1)与 s1中新的栈顶运算符相比较;(小于等于栈顶运算符时弹出栈顶的运算符)

  1. 遇到括号时:

  1. 如果是左括号,则直接压入 s1

  1. 如果是右括号,则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃

  1. 重复步骤 2-5 ,直到表达式的最右边

  1. 将 s1中剩余的运算符依次弹出并压入 s2

  1. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

/**
 * 中缀表达式转后缀表达式
 */
public class ReversePolandNotation {
    public static void main(String[] args) {

        //1.说明:因为直接对 str 进行操作不方便,因此先将 1+((2+3)*4)-5 放入到中缀表达式对应的 List
        //2.将得到的中缀表达式对应的 List 转换成 后缀表达式对应的 List
        String expression = "10+((2+3)*4)-5";
        List<String> infixExpressionList = toInfixExpressionList(expression);
        System.out.println(infixExpressionList);
        List<String> parseSuffixExpressionList = parseSuffixExpressionList(infixExpressionList);
        System.out.println(parseSuffixExpressionList);

        int res = calculate(parseSuffixExpressionList);
        System.out.println(res);
    }
    //1. 方法:将中缀表达式转成对应的 List
    public static List<String> toInfixExpressionList(String s){
        //定义一个 List,存放中缀表达式对应的内容
        List<String> ls = new ArrayList<>();
        int i = 0;//这时一个指针,用于遍历中缀表达式字符串
        String str;// 对多位数的拼接工作
        char c;//每遍历到一个字符,就放入到 c 中
        do{

            //如果 c 是一个非数字,我需要加入到 ls
            if((c=s.charAt(i)) < 48 ||(c=s.charAt(i)) > 57){
                ls.add(""+c);
                i++;//i需要后移
            }else{//如果是一个数,需要考虑多位数
                str = "";//先将 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;
    }

    //2. 方法:将得到的中缀表达式对应的 List 转换成 后缀表达式对应的 List
    public static List<String> parseSuffixExpressionList(List<String>ls){
        //定义一个栈
        Stack<String> s1 = new Stack<>();//符号栈
        //说明:因为 s2 这个栈 在整个转换过程中,没有pop 操作,而且后面我们还需要逆序输出
        //因此比较麻烦,因此我们就不使用 Stack<String>,直接使用 List<String> s2
//        Stack<String> s2 = new Stack<>();//中间结果栈
        List<String> s2 = new ArrayList<>();//存储中间结果的 List2

        //遍历 ls
        for (String item : ls) {
            //如果是一个数,加入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);
            }
        }
        //将s1 中剩余的运算符依次弹出并加入 s2
        while(s1.size()!=0){
            s2.add(s1.pop());
        }
        return s2;//因为是存放到一个 List中,因此按顺序输出就是对应的后缀表达式对应的 List
    }

    //完成对 逆波兰表达式的运算
    public static int calculate(List<String> ls){
        //创建栈,只需要一个栈即可
        Stack<String> stack = new Stack<>();
        //遍历 ls
        for (String item : ls) {
            //这里使用正则表达式来读取数
            if(item.matches("\\d+")){//匹配的是多位数
                //入栈
                stack.push(item);
            }else{
                //pop 出两个数,并运算,再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                switch(item){
                    case "+":
                        res = num1 + num2;
                        break;
                    case "-":
                        res = num1 - num2;
                        break;
                    case "*":
                        res = num1 * num2;
                        break;
                    case "/":
                        res = num1 / num2;
                        break;
                    default:
                        throw new RuntimeException("运算符有误");
                }
                //把 res 入栈
                stack.push(res+"");
            }
        }
        //最后留在 stack 中的数据就是运算结果
        return Integer.parseInt(stack.pop());
    }
}

//编写一个类 Operation 可以返回一个运算符对应的优先级
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){
            case "+":
                result = ADD;
                break;
            case "-":
                result = SUB;
                break;
            case "*":
                result = MUL;
                break;
            case "/":
                result = DIV;
                break;
            default:
                System.out.println("不存在该运算符");
                break;
        }
        return result;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值