栈的介绍:
注意: 栈和上次介绍的队列是不同的
用数组模拟栈的使用,由于栈是一种有序列表,可以使用数组的结构来存储栈的数据内容。下面用数组模拟栈的出栈、入栈等操作
实现栈的思路分析
韩老师代码如下
//定义一个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;
}
//入栈-push
public 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]);
}
}
}
自己写的代码如下
//无头结点的单链表模拟栈
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;
}
@Override
public 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;
}
}
使用栈完成表达式的计算思路
自己写的多位数代码如下
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保存到ch
int count = 0; //计数器 记录是几位数 放在符号栈的符号中
//开始while循环的扫描expression
while (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=1
operStack.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-num1
res = 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;
}
//入栈-push
public 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; //假定目前表达式只有加减乘除
}
}
//增加一个方法 可以返回当前栈顶的值 但是不是真正的pop
public 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保存到ch
String keepNum = ""; //用于拼接多位数
//开始while循环的扫描expression
while (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;
}
//入栈-push
public 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; //假定目前表达式只有加减乘除
}
}
//增加一个方法 可以返回当前栈顶的值 但是不是真正的pop
public 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;
}
}
在前面的基础上加上了判断小括号的功能,觉得还行 不过没加入判断负数的功能
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保存到ch
int count = 0; //计数器 记录是几位数 放在符号栈的符号中
boolean flag = false; //flag为真时 说明接收到了左、右括号 且还没有接收到符号位
//开始while循环的扫描expression
while (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=1
operStack.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-num1
res = 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-num1
res = 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;
}
//入栈-push
public 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个值 但是不是真正的pop
public 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;
}
}
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
例如:(3+4)×5-6对应的前缀表达式就是-×+3456,针对前缀表达式求值步骤如下:
其实从图3中很容易看出,逆波兰表达式的写法就是先把正常表达式中的数字从左到右依次写出来,然后在先算的数字后面加上相应的运算符,重复即可
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素和栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
例如: (3+4)×5-6对应的前缀表达式就是34+5×6-,针对后缀表达式求值步骤如下:
我们完成一个逆波兰计算器,要求完成如下任务:
自己写的代码如下
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) { //增强for
ts.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<>();
//遍历 ls
for (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) { //增强for
ts.add(si); //添加字符数组中的元素到列表中去
}
return ts;
}
可以看到,后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转成后缀表达式。
具体步骤如下:
比如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中的时候肯定就*排在+前面 所以先计算 *再算 +。
韩老师代码如下
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转化为后缀表达式对应的list
public static List<String> parseSuffixExpressionList(List<String> ls) {
Stack<String> s1 = new Stack<>(); //栈1存放运算符
//因为s2这个栈整个过程中没有pop操作 而且还需要逆序输出
//因此比较麻烦 就不用Stack 直接使用List s2
// Stack s2 = new Stack<>(); //栈2存放数字
List<String> s2 = new ArrayList<>(); //存储中间结果的List2
for (String item : ls) { //循环遍历每一个字符
if (item.matches("\\d+")) { //如果是个数
s2.add(item); //直接入库
} else if (item.equals("(")) { //左括号直接进s1
s1.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中剩余的运算符依次弹出并加入到s2
while (s1.size() != 0) {
s2.add(s1.pop());
}
return s2; //顺序输出就行
}
//方法:将中缀表达式转成对应的List
public static List<String> toInfixExpressionList(String s) {
//定义一个List 存放中缀表达式对应的内容
ArrayList<String> arrayList = new ArrayList<>();
int i = 0; //指针 用于遍历中缀表达式
String str = ""; //对多位数进行拼接
char c; //没遍历到一个字符 就放入到C
do {
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;
}
}