数据结构和算法-栈

数据结构和算法-栈

文章目录

  • 数据结构和算法-栈
    • 1. 栈的介绍
    • 2. 栈的应用场景
    • 3. 栈的快速入门
      • 3.1 用数组模拟栈
      • 3.2 课堂作业-用链表模拟栈
    • 4. 栈实现综合计算器
    • 4.1 课堂作业-加入小括号
    • 5. 栈的三种表达式-**前缀、中缀、后缀表达式(逆波兰表达式)**
      • 5.1 前缀表达式(波兰表达式)
        • 5.1.1 前缀表达式的计算机求值
      • 5.2 中缀表达式
      • 5.3 后缀表达式
        • 5.3.1 后缀表达式的计算机求值
      • 5.4 逆波兰计算器
    • 6. 中缀表达式转换为后缀表达式

1. 栈的介绍

栈的介绍:

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

注意: 栈和上次介绍的队列是不同的

  1. 栈的栈底是固定的,先入后出。一个指针
  2. 而队列的首尾都是不固定的,先入先出。两个指针

在这里插入图片描述

图1 出栈和入栈示意图

2. 栈的应用场景

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

3. 栈的快速入门

3.1 用数组模拟栈

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

  实现栈的思路分析

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

韩老师代码如下

//定义一个ArrayStack 表示栈
class ArrayStack {private int maxSize;    //栈的大小private int[] stack;    //数组 数组模拟栈 数据就放在该数组private int top = -1;   //top表示栈顶 初始化为-1 arr[top] 是栈的最后一个有效数据public ArrayStack(int maxSize) {this.maxSize = maxSize;stack = new int[maxSize];}//栈满public boolean isFull() {return top == maxSize - 1;}//栈空public boolean isEmpty() {return top == -1;}//入栈-pushpublic void push(int value) {//先判断是否满if (isFull()) {System.out.println("栈满");return;}stack[++top] = value;}//出栈-pop 将栈顶的数据返回public int pop() {//先判断栈是否空if (isEmpty()) {//抛出异常处理throw new RuntimeException("栈为空");}return stack[top--];}//显示栈的情况 遍历时 需要从栈顶开始显示public void list() {//先判断栈是否空if (isEmpty()) {//抛出异常处理throw new RuntimeException("栈为空");}for (int i = top; i >= 0; i--) {System.out.println(stack[i]);}}
}

3.2 课堂作业-用链表模拟栈

自己写的代码如下

//无头结点的单链表模拟栈
class ListStack {private final int maxSize;    //栈的最大容量private Node list = new Node(0);    //栈的第一个存储空间private int top = -1;   //指向栈顶public ListStack(int maxSize) {//maxSize 最大容量this.maxSize = maxSize;Node temp = list;for (int i = 1; i < maxSize; i++) {temp.setNext(new Node(i));  //将上一个节点连接新节点temp = temp.getNext();  //将temp指向新节点(链表的最后一个节点)}}//判断栈是否满了public boolean isFull() {return top == maxSize - 1;}//判断栈是否为空public boolean isEmpty() {return top == -1;}//入栈public void push(int value) {if (isFull()) {   //如果满了System.out.println("满了兄弟");return;//退出函数}Node temp = list;++top;while (temp.getNo() != top) {//当找到时退出循环temp = temp.getNext();}//当退出循环时 temp就是目标temp.setValue(value);}//出栈-pop 将栈顶的数据返回public int pop(){if (isEmpty()){ //说明为空throw new RuntimeException("空了");}Node temp = list;while (temp.getNo() != top) {//当找到时退出循环temp = temp.getNext();}top--;return temp.getValue();}//显示栈的情况 遍历时 需要从栈顶开始显示public void show(){if (isEmpty()){ //说明为空System.out.println("空的");}for (int i = 0; i < top + 1; i++) {Node temp = list;for (int j = 0; j < top - i; j++) {temp = temp.getNext();}System.out.println(temp.getValue());}}
}//单链表的节点
class Node {private int no; //编号private int value;  //保存的值private Node next;  //next域public Node(int no) {this.no = no;}public int getNo() {return no;}public void setNo(int no) {this.no = no;}public int getValue() {return value;}public void setValue(int value) {this.value = value;}public Node getNext() {return next;}public void setNext(Node next) {this.next = next;}@Overridepublic String toString() {return "Node{" +"no=" + no +", value=" + value +", next=" + ((next == null) ? "null" : next.hashCode()) +'}';}
}

看到弹幕说有直接头插法,试了一下,发现确实更好

//有头结点的单链表模拟栈
class ListStack2 {private final int maxSize;    //栈的最大容量private Node2 head = new Node2(); //头节点private int top = -1;   //指向栈顶public ListStack2(int maxSize) {this.maxSize = 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;//退出函数}Node2 temp = new Node2(value);//创建新节点//将此节点插入到头节点和旧的第一个节点中 成为新的第一个节点(栈顶)temp.setNext(head.getNext());head.setNext(temp);top++;//计数加一}//出栈-pop 将栈顶的数据返回public int pop() {if (isEmpty()) { //说明为空throw new RuntimeException("空了");}top--;  //计数减一int value = head.getNext().getValue();  //得到返回值//将第一个节点从链表中删除head.setNext(head.getNext().getNext()); //将头节点的next指向第二个节点 作为新的第一个节点return value;}//显示栈的情况 遍历时 需要从栈顶开始显示public void show() {if (isEmpty()) { //说明为空System.out.println("空的");return;}Node2 temp = head.getNext();while (temp != null) {//遍历完所有节点System.out.println(temp.getValue());temp = temp.getNext();//节点后移}}
}//单链表的节点
class Node2 {private int value;  //保存的值private Node2 next;  //next域public Node2(int value) {this.value = value;}public Node2() {}public int getValue() {return value;}public void setValue(int value) {this.value = value;}public Node2 getNext() {return next;}public void setNext(Node2 next) {this.next = next;}
}

4. 栈实现综合计算器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图2 思路图

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

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

自己写的多位数代码如下

package com.atguigu.stack;/*** @author 小小低头哥* @version 1.0*/public class Calculator {public static void main(String[] args) {//根据前面思路 完成表达式的运算
//        String expression = "2+2*3-2*1-1+2-3-2-3";
//        String expression = "2+3+1-4-3-2+2*20*450/50+6+4-3-4-2*2";String expression = "30*2-6*9+1";//创建两个栈 数栈、符号栈ArrayStack2 numStack = new ArrayStack2(30);ArrayStack2 operStack = new ArrayStack2(50);//定义需要的相关变量int index = 0;  //用于扫描int num1 = 0;int num2 = 0;int oper = 0;int res = 0;char ch = ' ';  //将每次扫描得到char保存到chint count = 0;  //计数器 记录是几位数 放在符号栈的符号中//开始while循环的扫描expressionwhile (true) {//依次得到expression的每一个字符ch = expression.substring(index, index + 1).charAt(0);//判断ch是什么 然后做出相应的处理if (operStack.isOper(ch)) {   //如果是运算符//判断当前的符号栈是否为空if (!operStack.isEmpty()) { //不为空则一个个判断operStack.push(count);  //先放进去//if (operStack.priority(ch) < operStack.priority(operStack.peek()))while (operStack.priority(ch) < operStack.priority(operStack.peek())) {//下面num1、oper、num2弹出的顺序不能变num1 = numStack.popN(operStack.pop());  //得到符号位前面的整数 并弹出符号栈中对应的数字标志oper = operStack.pop(); //出栈num2 = numStack.popN(operStack.pop());res = numStack.cal(num1, num2, oper);//把运算的结果放入数栈numStack.push(res);count = 1;if (!operStack.isEmpty()) {   //说明前面没有运算符了 自然也不需要更新了operStack.pop();   //把前一个运算符后面的数字位数去掉 因为此时已经变成res了 由于res是一个整体 所以相当于count=1operStack.push(count);   //更新前一个运算符后面的数字位数}else {break;}}}//为空或者判断完毕后 把当前符号入符号栈operStack.push(count);  //把符号前面的数是几位数记录下来 并放在ch的前面operStack.push(ch);count = 0;  //重新置零} else {//如果是数 则直接入数栈count++;    //数字数加一numStack.push(ch - 48); //转换为数字}//让index + 1 ,并判断是否扫描到expression最后index++;if (index >= expression.length()) {   //扫描结束operStack.push(count);  //扫描结束最后一个肯定是数字 需要把此数字的位数压入到符号栈break;}}while (true) {//下面num1、oper、num2弹出的顺序不能变num1 = numStack.popN(operStack.pop());  //得到符号位前面的整数 并弹出符号栈中对应的数字标志oper = operStack.pop(); //出栈num2 = numStack.popN(operStack.pop());if (!operStack.isEmpty() && oper == '-' && operStack.peek() == '-') {   //如果此时的操作符和上一个操作符都是负号//那么说明此时不是相减 而是相加res = numStack.cal(num1, num2, '+');} else if (!operStack.isEmpty() && oper == '+' && operStack.peek() == '-') {   //则应是num2-num1res = numStack.cal(num1, num2, '-');} else {res = numStack.cal(num1, num2, oper);}numStack.push(res); //入栈//如果符号栈为空 则计算到最后的结果,数栈中只有一个数字if (operStack.isEmpty()) {break;}operStack.pop();   //把前一个运算符后面的数字位数去掉 因为此时已经变成res了operStack.push(1);   //更新前一个运算符后面的数字位数}System.out.println(expression + "表达式的结果是:" + numStack.pop());}
}//先定义一个栈 直接使用前面创建好
//定义一个ArrayStack表示栈class ArrayStack2 {private int maxSize;    //栈的大小private int[] stack;    //数组 数组模拟栈 数据就放在该数组private int top = -1;   //top表示栈顶 初始化为-1 arr[top] 是栈的最后一个有效数据public ArrayStack2(int maxSize) {this.maxSize = maxSize;stack = new int[maxSize];}//栈满public boolean isFull() {return top == maxSize - 1;}//栈空public boolean isEmpty() {return top == -1;}//入栈-pushpublic void push(int value) {//先判断是否满if (isFull()) {System.out.println("栈满");return;}stack[++top] = value;}//出栈 连续出栈几个并组成数字 为数栈准备public int popN(int n) {int res = 0;for (int i = 0; i < n; i++) {if (i == 0) {res = res + pop();} else {res = res + pop() * (int) Math.pow(10, i); //从个位、十位等依次入手}}return res;}//出栈-pop 将栈顶的数据返回public int pop() {//先判断栈是否空if (isEmpty()) {//抛出异常处理throw new RuntimeException("栈为空");}return stack[top--];}//显示栈的情况 遍历时 需要从栈顶开始显示public void list() {//先判断栈是否空if (isEmpty()) {//抛出异常处理throw new RuntimeException("栈为空");}for (int i = top; i >= 0; i--) {System.out.println(stack[i]);}}//返回运算符的优先级 优先级是程序员确定的 优先级使用数组表示//数字越大 则优先级越高public int priority(int oper) {if (oper == '*' || oper == '/') {return 1;} else if (oper == '+' || oper == '-') {return 0;} else {return -1;  //假定目前表达式只有加减乘除}}//增加一个方法 可以返回当前栈顶的值 但是不是真正的poppublic int peek() {return stack[top - 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;}}

韩老师写的多位数代码如下

package com.atguigu.stack;/*** @author 小小低头哥* @version 1.0*/
public class Calculator2 {public static void main(String[] args) {//根据前面思路 完成表达式的运算String expression = "30*2-6*9+1";//创建两个栈 数栈、符号栈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保存到chString keepNum = "";   //用于拼接多位数//开始while循环的扫描expressionwhile (true) {//依次得到expression的每一个字符ch = expression.substring(index, index + 1).charAt(0);//判断ch是什么 然后做出相应的处理if (operStack.isOper(ch)) {   //如果是运算符//判断当前的符号栈是否为空if (!operStack.isEmpty()) { //不为空则一个个判断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 {//如果是数 则直接入数栈//分析思路//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))) {//如果后一位是运算符 则入栈numStack.push(Integer.parseInt(keepNum));keepNum = "";   //清空!!}}}//让index + 1 ,并判断是否扫描到expression最后index++;if (index >= expression.length()) {   //扫描结束break;}}while (true) {//如果符号栈为空 则计算到最后的结果,数栈中只有一个数字if (operStack.isEmpty()) {break;}num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop(); //出栈res = numStack.cal(num1, num2, oper);numStack.push(res); //入栈}System.out.println(expression + "表达式的结果是:" + numStack.pop());}
}//先定义一个栈 直接使用前面创建好
//定义一个ArrayStack表示栈class ArrayStack2 {private int maxSize;    //栈的大小private int[] stack;    //数组 数组模拟栈 数据就放在该数组private int top = -1;   //top表示栈顶 初始化为-1 arr[top] 是栈的最后一个有效数据public ArrayStack2(int maxSize) {this.maxSize = maxSize;stack = new int[maxSize];}//栈满public boolean isFull() {return top == maxSize - 1;}//栈空public boolean isEmpty() {return top == -1;}//入栈-pushpublic void push(int value) {//先判断是否满if (isFull()) {System.out.println("栈满");return;}stack[++top] = value;}//出栈-pop 将栈顶的数据返回public int pop() {//先判断栈是否空if (isEmpty()) {//抛出异常处理throw new RuntimeException("栈为空");}return stack[top--];}//显示栈的情况 遍历时 需要从栈顶开始显示public void list() {//先判断栈是否空if (isEmpty()) {//抛出异常处理throw new RuntimeException("栈为空");}for (int i = top; i >= 0; i--) {System.out.println(stack[i]);}}//返回运算符的优先级 优先级是程序员确定的 优先级使用数组表示//数字越大 则优先级越高public int priority(int oper) {if (oper == '*' || oper == '/') {return 1;} else if (oper == '+' || oper == '-') {return 0;} else {return -1;  //假定目前表达式只有加减乘除}}//增加一个方法 可以返回当前栈顶的值 但是不是真正的poppublic int peek() {return stack[top];}//判断是不是一个运算符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;}}

4.1 课堂作业-加入小括号

在前面的基础上加上了判断小括号的功能,觉得还行 不过没加入判断负数的功能

package com.atguigu.stack;/*** @author 小小低头哥* @version 1.0*/public class Calculator {public static void main(String[] args) {//根据前面思路 完成表达式的运算
//        String expression = "2+2*3-2*1-1+2-3-2-3";
//        String expression = "2+3+1-4-3-2+2*20*450/50+6+4-3-4-2*2";String expression = "30*2-(6*9)-(1+5*4)+(4*6)";//创建两个栈 数栈、符号栈ArrayStack2 numStack = new ArrayStack2(30);ArrayStack2 operStack = new ArrayStack2(50);//定义需要的相关变量int index = 0;  //用于扫描int num1 = 0;int num2 = 0;int oper = 0;int res = 0;char ch = ' ';  //将每次扫描得到char保存到chint count = 0;  //计数器 记录是几位数 放在符号栈的符号中boolean flag = false;   //flag为真时 说明接收到了左、右括号 且还没有接收到符号位//开始while循环的扫描expressionwhile (true) {//依次得到expression的每一个字符ch = expression.substring(index, index + 1).charAt(0);//判断ch是什么 然后做出相应的处理if (operStack.isOper(ch)) {   //如果是运算符//判断当前的符号栈是否为空if (!operStack.isEmpty()) { //不为空则一个个判断if (!flag) {//如果上一个是 ( 则不用放进去operStack.push(count);  //先放进去}flag = false;//if (operStack.priority(ch) < operStack.priority(operStack.peek()))while (operStack.priority(ch) < operStack.priority(operStack.peek(1))) {//0的优先权最小 不会进入循环 正要如此//下面num1、oper、num2弹出的顺序不能变num1 = numStack.popN(operStack.pop());  //得到符号位前面的整数 并弹出符号栈中对应的数字标志oper = operStack.pop(); //出栈num2 = numStack.popN(operStack.pop());res = numStack.cal(num1, num2, oper);//把运算的结果放入数栈numStack.push(res);count = 1;//前面还有运算符了 需要更新 或者计算完括号中的数if (!operStack.isEmpty() && operStack.peek(0) != 0) {operStack.pop();   //把前一个运算符后面的数字位数去掉 因为此时已经变成res了 由于res是一个整体 所以相当于count=1operStack.push(count);   //更新前一个运算符后面的数字位数} else {break;}}}//为空或者判断完毕后 把当前符号入符号栈operStack.push(count);  //把符号前面的数是几位数记录下来 并放在ch的前面operStack.push(ch);count = 0;  //重新置零} else if (ch == '(') {   //按数学规矩 (的前面一个肯定是运算符flag = true;operStack.push(0);  //把零送进去当作是起点} else if (ch == ')') {flag = true;operStack.push(count);  //)前必然是数字 所以这里先送进去一个计数器while (true) {//说明还没计算完括号内的运算//下面num1、oper、num2弹出的顺序不能变num1 = numStack.popN(operStack.pop());  //得到符号位前面的整数 并弹出符号栈中对应的数字标志oper = operStack.pop(); //出栈num2 = numStack.popN(operStack.pop());if (operStack.peek(0)!= 0 && oper == '-' && operStack.peek(1) == '-') {   //如果此时的操作符和上一个操作符都是负号//那么说明此时不是相减 而是相加res = numStack.cal(num1, num2, '+');} else if (operStack.peek(0)!= 0 && oper == '+' && operStack.peek(1) == '-') {   //则应是num2-num1res = numStack.cal(num1, num2, '-');} else {res = numStack.cal(num1, num2, oper);}numStack.push(res); //入栈//如果括号中的数计算完毕if (operStack.peek(0) == 0) {operStack.pop();    //把标志位给弹出来operStack.push(1);  //因为()的结果是一个整数 所以把1送进去作为()整体的数的个数break;}operStack.pop();   //把前一个运算符后面的数字位数去掉 因为此时已经变成res了operStack.push(1);   //更新前一个运算符后面的数字位数}} else {//如果是数 则直接入数栈count++;    //数字数加一numStack.push(ch - 48); //转换为数字}//让index + 1 ,并判断是否扫描到expression最后index++;if (index >= expression.length() ) {   //扫描结束//扫描结束最后一个肯定是数字 需要把此数字的位数压入到符号栈if( ch != ')'){ //但是如果最后是以)结束 则由于括号计算中已经把1插进去了 不需要了operStack.push(count);}break;}}while (true) {//下面num1、oper、num2弹出的顺序不能变num1 = numStack.popN(operStack.pop());  //得到符号位前面的整数 并弹出符号栈中对应的数字标志oper = operStack.pop(); //出栈num2 = numStack.popN(operStack.pop());if (!operStack.isEmpty() && oper == '-' && operStack.peek(1) == '-') {   //如果此时的操作符和上一个操作符都是负号//那么说明此时不是相减 而是相加res = numStack.cal(num1, num2, '+');} else if (!operStack.isEmpty() && oper == '+' && operStack.peek(1) == '-') {   //则应是num2-num1res = numStack.cal(num1, num2, '-');} else {res = numStack.cal(num1, num2, oper);}numStack.push(res); //入栈//如果符号栈为空 则计算到最后的结果,数栈中只有一个数字if (operStack.isEmpty()) {break;}operStack.pop();   //把前一个运算符后面的数字位数去掉 因为此时已经变成res了operStack.push(1);   //更新前一个运算符后面的数字位数}System.out.println(expression + "表达式的结果是:" + numStack.pop());}
}//先定义一个栈 直接使用前面创建好
//定义一个ArrayStack表示栈class ArrayStack2 {private int maxSize;    //栈的大小private int[] stack;    //数组 数组模拟栈 数据就放在该数组private int top = -1;   //top表示栈顶 初始化为-1 arr[top] 是栈的最后一个有效数据public ArrayStack2(int maxSize) {this.maxSize = maxSize;stack = new int[maxSize];}//栈满public boolean isFull() {return top == maxSize - 1;}//栈空public boolean isEmpty() {return top == -1;}//入栈-pushpublic void push(int value) {//先判断是否满if (isFull()) {System.out.println("栈满");return;}stack[++top] = value;}//出栈 连续出栈几个并组成数字 为数栈准备public int popN(int n) {int res = 0;for (int i = 0; i < n; i++) {if (i == 0) {res = res + pop();} else {res = res + pop() * (int) Math.pow(10, i); //从个位、十位等依次入手}}return res;}//出栈-pop 将栈顶的数据返回public int pop() {//先判断栈是否空if (isEmpty()) {//抛出异常处理throw new RuntimeException("栈为空");}return stack[top--];}//显示栈的情况 遍历时 需要从栈顶开始显示public void list() {//先判断栈是否空if (isEmpty()) {//抛出异常处理throw new RuntimeException("栈为空");}for (int i = top; i >= 0; i--) {System.out.println(stack[i]);}}//返回运算符的优先级 优先级是程序员确定的 优先级使用数组表示//数字越大 则优先级越高public int priority(int oper) {if (oper == '*' || oper == '/') {return 1;} else if (oper == '+' || oper == '-') {return 0;} else {return -1;  //假定目前表达式只有加减乘除}}//增加一个方法 可以返回当前栈顶的第n个值 但是不是真正的poppublic int peek(int n) {return stack[top - n];}//判断是不是一个运算符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;}}

5. 栈的三种表达式-前缀、中缀、后缀表达式(逆波兰表达式)

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

  1. 前缀表达式又称波兰式,前缀表达式的运算位于操作符之前
  2. 举例说明:(3+4)*5-6对应的前缀表达式就是- X + 3 4 5 6
5.1.1 前缀表达式的计算机求值

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

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

  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,由此得出最终结果

5.2 中缀表达式

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

5.3 后缀表达式

  1. 后缀表达式又称逆波兰表达式与前缀表达式相似,只是运算符位于操作数之后
  2. 中举例说明:(3+4)×5-6对应的后缀表达式就是34+5×6-

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图3 表达式的转化

其实从图3中很容易看出,逆波兰表达式的写法就是先把正常表达式中的数字从左到右依次写出来,然后在先算的数字后面加上相应的运算符,重复即可

5.3.1 后缀表达式的计算机求值

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

例如: (3+4)×5-6对应的前缀表达式就是34+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,由此得出最终结果

5.4 逆波兰计算器

我们完成一个逆波兰计算器,要求完成如下任务:

  1. 输入一个逆波兰表达式,使用栈(Stack),计算其结果
  2. 支持小括号和多位数整数,因为这里我们主要讲的是数据结构,因此计算器进行简化,只支持对整数的计算。
  3. 思路分析
  4. 代码完成

自己写的代码如下

package com.atguigu.stack;import java.util.ArrayList;
import java.util.List;
import java.util.Stack;/*** @author 小小低头哥* @version 1.0* 逆波兰 计算器实现*/
public class PolandNotation {public static void main(String[] args) {//先定义逆波兰表达式//(3+4)*5-6 => 3 4 + 5 * 6 -//3+4*(3-2) 3 4 3 2 - * +//3+(3-2)*1 3 3 2 - 1 * +//4 * 5 -8 + 60 + 8 /2 => 4 5 * 8 - 60 + 8 2 / +//说明为了方便 逆波兰表达式的数字和符号使用空格隔开
//        String suffixExpression = "3 4 + 5 * 6 -";
//        String suffixExpression = "3 4 3 2 - * +";
//        String suffixExpression = "3 3 2 - 1 * +";String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";List<String> arrayList = getArrayList(suffixExpression);//得到数组Stack<Integer> stack = new Stack<>();    //保存整数类的栈//使用栈的方式解决逆波兰计算器for (String string : arrayList) {   //遍历每一个元素if (isOper(string)) { //如果为操作符 则说明需要把栈中的两个元素出栈 进行计算int num1 = stack.pop();int num2 = stack.pop();int res = calculate(num1, num2, string);stack.push(res);    //将计算结果入栈} else { //说明是数字 则直接入栈stack.push(Integer.parseInt(string));}}System.out.println("结果为:" + stack.pop());}//接收一个字符串 通过空格分隔得到一个字符串数组 最后转换为列表public static List<String> getArrayList(String suffixExpression) {String[] s = suffixExpression.split(" ");List<String> ts = new ArrayList<>();for (String si : s) {  //增强forts.add(si); //添加字符数组中的元素到列表中去}return ts;}//写一个判断字符串是否是运算符的函数public static boolean isOper(String string) {if (string.equals("*") || string.equals("/") || string.equals("+") || string.equals("-")) {return true;}return false;}//写一个函数用来计算public static int calculate(int num1, int num2, String 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;}
}

韩老师写的如下

public class PolandNotation {public static void main(String[] args) {//先定义逆波兰表达式//(3+4)*5-6 => 3 4 + 5 * 6 -//3+4*(3-2) 3 4 3 2 - * +//3+(3-2)*1 3 3 2 - 1 * +//4 * 5 -8 + 60 + 8 /2 => 4 5 * 8 - 60 + 8 2 / +//说明为了方便 逆波兰表达式的数字和符号使用空格隔开
//        String suffixExpression = "3 4 + 5 * 6 -";
//        String suffixExpression = "3 4 3 2 - * +";
//        String suffixExpression = "3 3 2 - 1 * +";String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";List<String> arrayList = getArrayList(suffixExpression);//得到数组int res = calculate(arrayList);System.out.println("计算的结果是:" + res);}public static int calculate(List<String> ls) {//创建给栈 只需要一个栈即可Stack<String> stack = new Stack<>();//遍历 lsfor (String item : ls) {//这里使用正则表达式来取出数//matches是整体匹配的意思 一般用来验证输入的字符串是否满足条件if (item.matches("\\d+")) {   // \\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(res + "");}}//最后留在栈中的就为最后的结果return Integer.parseInt(stack.pop());}//接收一个字符串 通过空格分隔得到一个字符串数组 最后转换为列表public static List<String> getArrayList(String suffixExpression) {String[] s = suffixExpression.split(" ");List<String> ts = new ArrayList<>();for (String si : s) {  //增强forts.add(si); //添加字符数组中的元素到列表中去}return ts;}

6. 中缀表达式转换为后缀表达式

  可以看到,后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转成后缀表达式。

具体步骤如下:

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

比如3+4+5 * 6 * 8-4 => 3 4 + 5 6 * 8 * + 4 - (或者3+4+5 * 6 * 8-4 => 3 4 5 6 * 8 * + + 4 -),先判断3+4+ 由于第一个+后面还是+,所以第一个+肯定可以运算 我直接入s2栈 此时s2栈就变成了3 4 +,此时参考符号又变成了第二个+,那么再看第三个运算符* 优先级高于+ 所以放在+后面 都是s1栈,最后s1全部弹栈到s2中的时候肯定就*排在+前面 所以先计算 *再算 +。

在这里插入图片描述

图4 思路图

韩老师代码如下

   public static void main(String[] args) {//完成将一个中缀表达式转成后缀表达式的形式//说明//1. (3+4)*5-6 => 3 4 + 5 * 6 -//2. 直接对str进行操作不方便 先将其转化为List的形式String expression = "1+((2+3)*4)-5";List<String> infixExpressionLis = toInfixExpressionList(expression);System.out.println("中缀表达式对应的List=" + infixExpressionLis);List<String> parseSuffixExpression = parseSuffixExpressionList(infixExpressionLis);System.out.println("后缀表达式对应的List=" + parseSuffixExpression);}//将中缀表达式list转化为后缀表达式对应的listpublic static List<String> parseSuffixExpressionList(List<String> ls) {Stack<String> s1 = new Stack<>();   //栈1存放运算符//因为s2这个栈整个过程中没有pop操作 而且还需要逆序输出//因此比较麻烦 就不用Stack<String> 直接使用List<String> s2
//        Stack<String> s2 = new Stack<>();   //栈2存放数字List<String> s2 = new ArrayList<>();   //存储中间结果的List2for (String item : ls) {   //循环遍历每一个字符if (item.matches("\\d+")) { //如果是个数s2.add(item);   //直接入库} else if (item.equals("(")) { //左括号直接进s1s1.push(item);} else if (item.equals(")")) { //如果是右括号//如果是右括号“)",则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃while (!s1.peek().equals("(")) { //直到遇见左括号s2.add(s1.pop());   //把s1中的数据依次弹出并放入到s2中}s1.pop();   //别忘了最后将左括号弹出} else {//当item的优先级小于等于栈顶的优先级 则将s1栈顶的运算符弹出并加入到s2中 并继续比较while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {s2.add(s1.pop());}//还需要将item压入栈中s1.push(item);}}//将s1中剩余的运算符依次弹出并加入到s2while (s1.size() != 0) {s2.add(s1.pop());}return s2;  //顺序输出就行}//方法:将中缀表达式转成对应的Listpublic static List<String> toInfixExpressionList(String s) {//定义一个List 存放中缀表达式对应的内容ArrayList<String> arrayList = new ArrayList<>();int i = 0;  //指针 用于遍历中缀表达式String str = ""; //对多位数进行拼接char c; //没遍历到一个字符 就放入到Cdo {if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {//如果不是一个数字arrayList.add("" + c);i++;} else { //后移 考虑多位数的问题str = "";while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57) {str += c;   //多位数拼接i++;}arrayList.add(str);}} while (i < s.length());return arrayList;   //返回列表}class Operation {private static int ADD = 1;private static int SUB = 1;private static int MUL = 1;private static int DIV = 1;//写一个方法 返回对应的优先级数字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;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/221227.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中低压MOS 适用于电子烟等产品—— 较小的开关损耗 过流能力好

工作原理&#xff1a; 当用户在吸嘴处抽吸时&#xff0c;气流经过进气孔&#xff0c;穿 过电路板上方的咪头&#xff0c;咪头即产生电信号&#xff0c;驱 动芯片板&#xff0c;让电池供电给雾化芯&#xff0c;雾化芯中的 发热丝将电能转化成热能&#xff0c;当温度达到雾化液…

LeetCode-2487. 从链表中移除节点【栈 递归 链表 单调栈】

LeetCode-2487. 从链表中移除节点【栈 递归 链表 单调栈】 题目描述&#xff1a;解题思路一&#xff1a;可以将链表转为数组&#xff0c;然后从后往前遍历&#xff0c;遇到大于等于当前元素的就入栈&#xff0c;最终栈里面的元素即是最终的答案。解题思路二&#xff1a;递归&am…

【一步到位】汽车过户全攻略:轻松搞定,告别繁琐流程

校长车行是一家昆明二手车代办公司&#xff0c;今天我们要聊一聊一个让很多人头疼的问题——汽车过户。相信很多朋友在购买二手车或者需要将车辆转让给他人时&#xff0c;都会遇到这个繁琐的流程。那么&#xff0c;如何才能轻松搞定汽车过户呢&#xff1f;接下来&#xff0c;就…

(0-1)分布

假设离散型随机变量X只可能取到0、1两个值&#xff0c;它的分布律为&#xff1a; &#xff0c;其中&#xff0c; 那么称X服从参数为p的0-1分布&#xff0c;也叫两点分布。 其实上面公式就是将下面两个式子写在一起&#xff1a;

【Hive_02】查询语法

1、基础语法2、基本查询&#xff08;Select…From&#xff09;2.1 全表和特定列查询2.2 列别名2.3 Limit语句2.4 Where语句2.5 关系运算函数2.6 逻辑运算函数2.7 聚合函数 3、分组3.1 Group By语句3.2 Having语句3.3 Join语句&#xff08;1&#xff09;等值与不等值Join&#x…

SUPER-ADAM: Faster and Universal Framework of Adaptive Gradients

这周看了啥&#xff1a; 本周主要来看看别人是如何证明收敛的&#xff0c;围绕算法SUPER-ADAM 的更新过程和论文后面的证明&#xff0c;&#xff08;这篇证明比上周的亲切多了&#xff0c;我哭死&#xff09;仔细看了证明每一步的推导&#xff08;至于作者如何想出的&#xff…

verilog基础语法之比较器

逻辑运算符以及逻辑电路概述 逻辑运算符常用于条件判断语句&#xff0c;输出为布尔值True/False。逻辑运算符是基于比较器构造的。比较器电路是产生逻辑比较的本质&#xff1b;比较器电路的复杂度与位宽和比较类型相关&#xff1b;一般情况下可以先构造基本比较器&#xff0c;…

原生Html 引入element UI + vue3 表单校验设置

效果&#xff1a; 提交时&#xff0c;检验结果展示 html源码 <!DOCTYPE html> <html> <!--带搜索输入框下拉弹窗 --> <head><meta charset"UTF-8"><!-- import Vue before Element --><script src"../js/vue3.3.8/vu…

jmeter,通过Ant插件生成html报告,展示接口详细信息

一、下载Ant 下载地址&#xff1a;Apache Ant - 二进制发行版 二、安装 1、Ant环境变量 解压Ant目录&#xff1b;配置系统环境变量&#xff0c;添加ANT_PATH&#xff0c;值为D:\Software\Ant_plugIn\apache-ant-1.10.14配置系统环境变量Path&#xff0c;添加Ant路径 %ANT_H…

Unity之OpenXR+XR Interaction Toolkit接入Meta Quest3

前言 随着备受期待的Meta Quest 3与今年10月10日发布,这款来自Meta的下一代VR游戏头戴设备承诺将彻底改变您的游戏方式。 Meta Quest 3,玩家只需轻松一触即可在虚拟现实和真实世界之间无缝切换,无需摘下头戴设备进行快速现实检查。 Meta Quest 3最引人注目的特点之一是其能…

webpack学习-5.代码分离

webpack学习-5.代码分离 1.入口起点2.防止重复2.1 入口依赖2.2 SplitChunksPlugin 3.动态导入3.1 使用符合 ECMAScript 提案 的 import() 语法3.2 使用 webpack 特定的 require.ensure 4.预获取/预加载模块5.分析bundle6.总结 1.入口起点 代码分离是 webpack 中最引人注目的特…

AIGC - 环境搭建

一. 硬件环境 1. 超微7048主板&#xff0c;最多可搭载4块GPU 2. 2个Intel的 Xen至强 14核 CPU 3. 目前安装了一块Nvidia 的P40 GPU&#xff0c;后续根据需要还最多可以扩展3块GPU 4. 4T机械 2T Nvme固态&#xff0c; 5. 4条64G DDR4内存条&#xff0c;共 196G内存…

QT多项目管理

.pro文件配置解释&#xff1a;​​​​​​ Qt 中的多项目管理_qt子目录项目-CSDN博客Qt 模块化开发之 pro 子项目开发_qt 子项目-CSDN博客关于Qt编译库&#xff08;1&#xff09;&#xff1a;在子项目中编译动态库并且使用_qt编译动态库后配置qt-CSDN博客QT release下的编译…

涵盖多种功能,龙讯旷腾Module第六期:输运性质

Module是什么 在PWmat的基础功能上&#xff0c;我们针对用户的使用需求开发了一些顶层模块&#xff08;Module&#xff09;。这些Module中的一部分是与已有的优秀工具的接口&#xff0c;一部分是以PWmat的计算结果为基础得到实际需要的物理量&#xff0c;一部分则是为特定的计…

排序算法(二)-冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、基数排序

排序算法(二) 前面介绍了排序算法的时间复杂度和空间复杂数据结构与算法—排序算法&#xff08;一&#xff09;时间复杂度和空间复杂度介绍-CSDN博客&#xff0c;这次介绍各种排序算法——冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、基数排序。 文章目录 排…

排序-归并排序与计数排序

文章目录 一、归并排序1、概念2、过程3、代码实现4、复杂度5、稳定性 二、 计数排序1、思路2、代码实现3、复杂度&#xff1a;4、稳定性 一、归并排序 1、概念 是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已…

关键点检测☞png格式换bmp,且labelme标注的json中imagePath同步修改格式

import os import cv2 import jsondef bmp2jpg(in_img_path, out_dir_name): # .png -> .bmp# img = cv2.imread(in_img_path) # 彩色图片,位深24img =</

GDPU 数据结构 天码行空13

文章目录 一、【实验目的】二、【实验内容】三、实验源代码四、实验结果五、实验总结 一、【实验目的】 (1) 理解插入排序算法的实现过程&#xff1b; &#xff08;2&#xff09;理解不同排序算法的时间复杂度及适用环境&#xff1b; &#xff08;3&#xff09;了解算法性能…

Win11 跑通tensorRT

准备 1.安装cuda&#xff0c;成功之后文件夹如下图所示 2.下载cudnn&#xff0c;把cudnn对应的文件放在cuda里面 3.安装vs 4.安装对应cuda版本的tensorRT https://developer.nvidia.com/tensorrt-download 5.opencv安装 编译好 打开vs&#xff0c;配置环境 用vs打开tens…

PLC-Recorder V3版本软件升级方法

PLC-Recorder V3软件进行了架构优化&#xff0c;包括采集服务器、客户端、授权管理等组件。升级方法与V2版本相似&#xff0c;但是也有一些变化&#xff0c;说明如下&#xff1a; 一、从V2向V3版本升级 1、退出原PLCRecorder&#xff1a;关闭右下角的图标。 2、退出打开的离线…