应用场景
银行排队区号,先到先得,先进先出。。。
队列是一个有序列表,可以用数组或是链表来实现。【任何线性数据结构都有两种存储结构来实现。】
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
遵循 先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出。
队列本身是有序列表,若使用数组的结构来存储队列的数据,而队列数组的声明如原理图所示,MaxSize 是该队列的最大容量。
因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 front 及 rear 分别记录队列前后端的下标,front 会随着数据输出而改变,而 rear 则是随着数据输入而改变。
当我们将数据存入队列时称为 “ addQueue ”, addQueue 的处理需要有两个步骤:思路分析
将尾指针往后移:rear + 1,当 front == rear【空】
若尾指针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];
}
}
问题分拟
目前数组使用一次就不能用,没有达到复用的效果。
将这个数组使用算法,改进成一个环形的数组 %(用到取模)
对前面的数组模拟队列进行优化,充分利用数组。因此将数组看做一个环形的。(通过取模的方式)
为了避免当只有一个元素时,对头和队尾重合使处理变得麻烦,现调整 front 和 rear 变量(指针)的含义:
front:指向队列的第一个元素,也就是说 arr[front] 就是队列的第一个元素。front 的初始值 = 0;
rear:指向队列的最后一个元素的下一个位置。rear 的初始值 = 0;希望空出一个空间作为约定。【此时当 front 等于 rear 时,此队列就没有剩下元素,而是空队列】
如何表示队列满了的条件:当数组全部被填满时,此时 front = rear,该条件与空队列条件重合,因此无法判断!
方法一:设置一个标志变量 flag,当 front == rear,且 flag=0 时,队列为空;当 front == rear,且 flag=1 时,队列为满。
方法二:当队列为空时,条件就是 front == rear,当队列满时,我们修改其条件,保留一个元素空间。也就是说,队列满时,数组中还有一个空闲单元。【约定】
所以尽管 rear 和 front 只相差一个位置时就是满的情况,但也可能是相差整整一圈。所以若队列的最大尺寸为 maxSize,那么队列满的条件时:(rear+1)%maxSize == front (取模%的目的就是为了整合 rear 和 front 大小为一个问题)
队列中有效的数据的个数(通用计算队列长度公式):(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];
}
}
栈的定义:是限定仅在表尾进行插入和删除操作的线性表。这里说的表尾指的是 栈顶。
把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又被称为 先进后出的线性表。
栈的插入操作叫做进栈,也称为压栈(push)、入栈。 栈的删除操作叫做出栈,也叫弹栈(pop)。
栈的应用场景:
子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入到堆栈中。【编译器使用栈实现了递归】【在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态】。
表达式的转换【中缀表达式转后缀表达式】与求值
二叉树的遍历
图形的深度优先搜索法。
栈的顺序存储结构 ——数组模拟
选择下标为 0 的一端作为栈底,因为首元素变化量最小
实现顺序栈的思路模拟:
使用数组来模拟栈,定义储栈的长度 StackSize;
定义一个 top 变量来表示栈顶,初始化为 -1。因此空栈的判定条件定位 top == -1;
入栈的操作:当有数据加入到栈时,top++;stack[ top ] = data;
出栈的操作: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 +
'}';
}
}
思路:
通过一个 index 值(索引),来遍历我们的表达式
如果我们发现是一个数字,就直接入数栈
如果发现扫描到的是一个符号,就分如下情况:
如果发现当前的符号栈为空,就直接入栈
如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中 pop 出两个数,在从符号栈中 pop 出一个符号,进行运算,将得到的结构入数栈,然后将当前的操作符入符号栈。如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈。
当表达式扫描完毕,就顺序的从数栈和 符号栈中 pop 出相应的数和符号,并运行。
最后在数栈只有一个数字,就是表达式的结果。
/**
* 栈的应用——模拟计算器(中缀表达式)
*/
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];
}
}
前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
举例说明(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6
前缀表达式的求值流程:
从右到左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程,直到表达式最左端,最后运算得出的值即为表达式的结果。
中缀表达式就是常见的运算表达式
中缀表达式的求值是我们最熟悉的,但是对计算机来说却不好操作,因此在计算结果时,往往会将中缀表达式转成其他表达式来操作(一般转成后缀表达式。)
后缀表达式又称为逆波兰表达式。只是运算符位于操作数之后
后缀表达式的求值流程:
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程,直到表达式最右端,最后运算得出的值即为表达式的结果。
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 rpnList = getListString(suffixExpression);
System.out.println(rpnList);
int res = calculate(rpnList);
System.out.println(res);
}
public static List getListString(String suffixExpression){
String[] split = suffixExpression.split(" ");
List list = new ArrayList<>();
for (String ele : split) {
list.add(ele);
}
return list;
}
//完成对 逆波兰表达式的运算
public static int calculate(List ls){
//创建栈,只需要一个栈即可
Stack 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());
}
}
具体步骤
初始化两个栈:运算符栈s1 和存储中间结果的栈 s2;
从左至右扫描中缀表达式;
遇到操作数时,将其压入 s2
遇到运算符时,比较其与s1栈顶运算符的优先级:
如果 s1 为空,或栈顶运算符为左括号“( ”,则直接将此运算符入栈;
否则,若优先级比栈顶运算符的高,也将运算符压入 s1;
否则,将s1栈顶的运算符弹出并压入到 s2中,再次转到(4-1)与 s1中新的栈顶运算符相比较;(小于等于栈顶运算符时弹出栈顶的运算符)
遇到括号时:
如果是左括号,则直接压入 s1
如果是右括号,则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃
重复步骤 2-5 ,直到表达式的最右边
将 s1中剩余的运算符依次弹出并压入 s2
依次弹出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 infixExpressionList = toInfixExpressionList(expression);
System.out.println(infixExpressionList);
List parseSuffixExpressionList = parseSuffixExpressionList(infixExpressionList);
System.out.println(parseSuffixExpressionList);
int res = calculate(parseSuffixExpressionList);
System.out.println(res);
}
//1. 方法:将中缀表达式转成对应的 List
public static List toInfixExpressionList(String s){
//定义一个 List,存放中缀表达式对应的内容
List 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 parseSuffixExpressionList(Listls){
//定义一个栈
Stack s1 = new Stack<>();//符号栈
//说明:因为 s2 这个栈 在整个转换过程中,没有pop 操作,而且后面我们还需要逆序输出
//因此比较麻烦,因此我们就不使用 Stack,直接使用 List s2
// Stack s2 = new Stack<>();//中间结果栈
List 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 ls){
//创建栈,只需要一个栈即可
Stack 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;
}
}