4、栈
4.1 概念介绍
栈:stack,一种先进后出(first in last out FILO
)的有序列表; 元素的插入和删除只能在线性表的同一端进行,允许进行插入和删除操作的一端被称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。插入元素一般被称为入栈(push),删除元素一般被称为出栈(pop)。
元素入栈示意图:
元素出栈:
4.2 数组模拟栈
- **思路分析:**数组模拟栈,需要定义数组大小,指向栈顶的指针top,初始化为-1;
入栈:先判断栈是否满,栈顶指针++,stack[top] = value
出栈:先判断栈是否空,临时变量保存栈顶元素,栈顶指针top–;返回栈顶元素
- 代码实现:
package com.kevin.arrayStack;
/**
* @author : kevin ding
* @date : 2022/2/27 10:42
* @description : 用数组模拟栈
*/
public class ArrayStack {
private int maxSize;
private int top;
private int[] arrayStack;
// 构造器
public ArrayStack(int maxSize){
// 初始化容量
this.maxSize = maxSize;
this.top = -1;
// 初始化容量为maxSize大小的容数组
this.arrayStack = new int[this.maxSize];
}
// 判断栈是否为空 当top值为初始化的值时,栈为空
public boolean isEmpty(){
return this.top == -1;
}
// 判断栈是否满: 当top指向栈顶时,top == maxSize-1 时,栈满
public boolean isFull(){
return this.top == this.maxSize-1;
}
// 入栈
public void push(int value){
// 首先判断是否栈满
if(isFull()){
System.out.println("栈满,无法将元素入栈...");
return;
}
// 进行入栈操作:
top += 1;
arrayStack[top] = value;
}
// 出栈
public int pop(){
// 首先判断栈是否为空
if(isEmpty()){
throw new RuntimeException("栈为空,没有元素可出栈...");
}
// 元素出栈
int curValue = arrayStack[top];
top -= 1;
return curValue;
}
// 显示栈顶元素
public int peek(){
// 首先判断栈是否为空
if(isEmpty()){
throw new RuntimeException("栈为空,栈顶没有元素...");
}
return arrayStack[top];
}
// 遍历栈的元素
public void showAll(){
// 判断栈是否为空
if(isEmpty()){
System.out.println("栈为空...");
return;
}
// 从栈顶开始遍历所有元素
for(int i = top; i>=0; i--){
System.out.println("arrayStack[" + i + "]=" + arrayStack[i]);
}
}
}
- 验证:
package com.kevin.arrayStack;
import java.util.Scanner;
/**
* @author : kevin ding
* @date : 2022/2/27 11:03
* @description : 数组模拟栈的测试类
*/
public class ArrayStackTestDemo {
public static void main(String[] args) {
// 创建一个ArrayStack对象表示栈
ArrayStack arrayStack = new ArrayStack(5);
// 通过界面操作来验证功能的实现
String choose = "";
boolean loop = true;
Scanner scanner = new Scanner(System.in);
// 进行循环验证
while (loop){
// 显示控制台提示信息
System.out.println("pop: 将元素入栈");
System.out.println("push: 栈顶元素出栈");
System.out.println("show: 遍历栈中所有的元素");
System.out.println("peek: 显示栈顶的元素(不出栈)");
System.out.println("exit: 退出程序");
System.out.println("请输入要选择的操作:");
choose = scanner.next();
switch (choose){
case "pop":
try {
int value = arrayStack.pop();
System.out.println("出栈的元素是:" + value);
}catch (Exception exception){
exception.printStackTrace();
}
break;
case "push":
System.out.println("请输入要入栈的元素:");
int value = scanner.nextInt();
arrayStack.push(value);
break;
case "show":
arrayStack.showAll();
break;
case "peek":
try {
int peekValue = arrayStack.peek();
System.out.println("栈顶元素是:" + peekValue);
}catch (Exception exception){
exception.printStackTrace();
}
break;
case "exit":
scanner.close();
loop = false;
break;
default:
System.out.println("选择指令输入有误...");
break;
}
}
System.out.println("退出程序...");
}
}
4.3 栈的应用
4.3.1 中缀表达式计算器
使用栈来完成中缀表达式的计算:
假设输入表达式:2*7-4+20-62/2-5 的值
思路分析:
-
定义索引指针,来遍历表达式中的所有元素,并初始化两个栈用于存放遍历到的数字和运算符号
-
如果遍历到的元素是数字,直接进入存放数字的栈
-
如果遍历到的元素是符号,分如下情况:
-
符号栈为空:直接将符号入栈
-
符号栈不为空
-
当前符号优先级小于等于栈顶符号优先级:
从数栈中取出两个栈顶的元素,符号栈中取出一个元素,进行运算,得到的结果存放到数栈中,随后将当前的符号入栈
-
当前符号优先级大于栈顶符号优先级:直接入栈
-
-
-
表达式扫描完毕,顺序的从数栈和符号栈中弹出元素进行运算,并将运算结果存入到数栈中;
-
直到最后,符号栈为空,在数栈中只有一个元素,即为表达式的运算结果
代码实现:
- 创建一个栈:
package com.kevin.arrayStack;
/**
* @author : kevin ding
* @date : 2022/2/27 13:30
* @description : 中缀表达式计算器专用栈
*/
public class CalculatorStack {
private int maxSize;
private int top;
private int[] arrayStack;
// 构造器
public CalculatorStack(int maxSize){
// 初始化容量
this.maxSize = maxSize;
this.top = -1;
// 初始化容量为maxSize大小的容数组
this.arrayStack = new int[this.maxSize];
}
// 判断栈是否为空 当top值为初始化的值时,栈为空
public boolean isEmpty(){
return this.top == -1;
}
// 判断栈是否满: 当top指向栈顶时,top == maxSize-1 时,栈满
public boolean isFull(){
return this.top == this.maxSize-1;
}
// 入栈
public void push(int value){
// 首先判断是否栈满
if(isFull()){
System.out.println("栈满,无法将元素入栈...");
return;
}
// 进行入栈操作:
top += 1;
arrayStack[top] = value;
}
// 出栈
public int pop(){
// 首先判断栈是否为空
if(isEmpty()){
throw new RuntimeException("栈为空,没有元素可出栈...");
}
// 元素出栈
int curValue = arrayStack[top];
top -= 1;
return curValue;
}
// 显示栈顶元素
public int peek(){
// 首先判断栈是否为空
if(isEmpty()){
throw new RuntimeException("栈为空,栈顶没有元素...");
}
return arrayStack[top];
}
// 遍历栈的元素
public void showAll(){
// 判断栈是否为空
if(isEmpty()){
System.out.println("栈为空...");
return;
}
// 从栈顶开始遍历所有元素
for(int i = top; i>=0; i--){
System.out.println("arrayStack[" + i + "]=" + arrayStack[i]);
}
}
// 判断给定元素的优先级方法
// 优先级高的 数字越大
public int getPriority(int operator){
if(operator == '*' || operator == '/'){
return 1;
}else if(operator == '+' || operator == '-'){
return 0;
}else{
// 假定 目前操作符只有 + - * /
return -1;
}
}
// 判断给定的元素是否为操作符:
public boolean isOperator(char ch){
// 给定字符为+ - * / 的任一操作符,则返回true
return (ch=='+' || ch=='-' || ch=='*' || ch=='/');
}
// 对于给定的运算符和参数 进行运算
// 先出栈的数被num2接收,后出栈的数被num1接收,所以在进行操作时 为num1-num2 num1/num2
public int calculate(int num1, int num2, int operator){
int res = 0; // 用于存放计算结果
if(operator == '+'){
res = num1 + num2;
}else if (operator == '-'){
res = num1 - num2;
}else if (operator == '*'){
res = num1 * num2;
}else if(operator == '/'){
res = num1 / num2;
}
return res;
}
}
- 计算器实现:
package com.kevin.arrayStack;
/**
* @author : kevin ding
* @date : 2022/2/27 14:02
* @description : 使用栈来实现一个综合计算器(中缀表达式)
* 计算 7*2+8/4+6-5*3
* 使用栈完成计算的思路:
* 1. 使用一个指针 来遍历表达式
* 2. 如果当前指向的是数字,直接入数字的栈
* 3. 如果当前指向的是运算符:运算符的栈为空,直接入栈;如果不为空:若当前运算符优先级较高,则直接入栈,
* 若当前运算符优先级小于等于栈顶运算符的优先级,则将数栈中pop出两个,运算符栈pop出一个,进行运算,运算结果在push到数栈
* 4.当表达式扫描完毕,就顺序的从数栈和符号栈中pop相应的元素进行运算,
* 5. 最后在数栈中只有一个数字,即为表达式的结果
*
*/
public class ArrayStackCalculator {
public static void main(String[] args) {
String expression = "7*2+8/4+60+22-5*3";
// 1.准备工作 :创建两个栈,一个是数栈,一个是运算符栈;指针扫描,临时变量存储数栈pop的元素和操作符,计算结果
CalculatorStack numStack = new CalculatorStack(10);
CalculatorStack operatorStack = new CalculatorStack(10);
int index = 0;
int num1 = 0;
int num2 = 0;
int operator = 0;
int res = 0;
char curChar= ' '; // 每次将扫描到的结果保存到ch中
String nums = ""; // 用于对多位数进行处理
// 2.循环扫描表达式 获取表达式中的每个字符
while(true){
curChar = expression.charAt(index);
// 判断这个当前字符是什么
// 如果是运算符
if(operatorStack.isOperator(curChar)){
// 如果运算符的栈不为空,进行优先级的比较
if(! operatorStack.isEmpty()){
// 进行优先级的比较:
if(operatorStack.getPriority(curChar) <= operatorStack.getPriority(operatorStack.peek())){
num2 = numStack.pop();
num1 = numStack.pop();
operator = operatorStack.pop();
res = numStack.calculate(num1, num2, operator);
// 将计算结果入数栈
numStack.push(res);
// 随后将当前的操作符如栈
operatorStack.push(curChar);
}else {
// 当前的优先级较高,则直接入栈
operatorStack.push(curChar);
}
}else{
// 运算符的栈为空 当前运算符直接入栈
operatorStack.push(curChar);
}
}else{
// 当前为数字 直接入数栈 入栈时由于是字符类型,需要转换成数对应的ASCII码,从字符1转换成数字的1入栈
// numStack.push(curChar-48);
// 不能当前扫描的是数字就进栈,有可能是个多位数,需要接着往后判断
// 需要再往index后面看一位,直到发现后面是符号,才将当前的数入栈
nums += curChar;
// 判断 如果curChar已经是最后一位,直接将nums入栈
if (index == expression.length()-1){
numStack.push(Integer.parseInt(nums));
}else {
// 判断index的下一位是数字还是符号,是数字就继续下一轮的扫描不如栈,如果是符号就将nums入栈
if(operatorStack.isOperator(expression.charAt(index+1))){
numStack.push(Integer.parseInt(nums));
// 入栈之后 将num清空,方便下一轮的运算
nums = "";
}
}
}
// 指针后移
index ++;
if(index >= expression.length()){
break;
}
}
// 当表达式扫描完毕,就顺序的从数栈和符号栈中pop相应的数和符号进行运算
while(true){
//如果符号栈为空,计算到最后的结果
if(operatorStack.isEmpty()){
break;
}
num2 = numStack.pop();
num1 = numStack.pop();
operator = operatorStack.pop();
res = numStack.calculate(num1, num2, operator);
numStack.push(res);
}
// 最后数栈中的数即为结果
int result = numStack.pop();
System.out.println("计算结果为:" + result);
}
}
4.3.2 逆波兰计算器
逆波兰表达式(后缀表达式):3 4 * 5 + 12 -
思路分析:
- 定义指针从左到右开始扫描表达式,当前元素是数字,将其进行入栈
- 当前元素是运算符,从栈中取出两个数字进行运算,并将运算结果入栈
- 直到表达式遍历结束,栈中仅有一个元素,即为最终的运算结果
代码实现:
package com.kevin.arrayStack;
import com.sun.corba.se.spi.ior.IORFactories;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* @author : kevin ding
* @date : 2022/2/27 14:44
* @description : 逆波兰表达式运算器
*/
public class PolandExpressionCalculator {
public static void main(String[] args) {
String expression = "18 5 * 24 - 8 +";
// 对于字符串进行遍历不方便,将其元素存放于数组中进行遍历
List<String> expressionList = expressionToList(expression);
// 调用计算器方法
int res = polandCalculator(expressionList);
System.out.println("运算结果为:" + res);
}
private static int polandCalculator(List<String> expressionList) {
// 首先创建栈 用于存放遍历到的数字
Stack<String> stack = new Stack<>();
// 遍历数组
for (String s : expressionList) {
// 若当前元素是数字,直接入栈
if(s.matches("\\d+")){
stack.push(s);
}else{
// 否则弹出两个元素进行运算,需要用后弹出的进行运算
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
if(s.equals("+")){
res = num1 + num2;
}else if (s.equals("-")){
res = num1 - num2;
}else if(s.equals("*")){
res = num1 * num2;
}else if(s.equals("/")){
res = num1 / num2;
}else {
throw new RuntimeException("运算符有误,请检查表达式...");
}
// 将计算结果入栈
stack.push("" + res);
}
}
// 最后,栈中的元素即为运算结果
return Integer.parseInt(stack.pop());
}
private static List<String> expressionToList(String expression) {
String[] split = expression.split(" ");
List<String> list = new ArrayList<>();
for (String s : split) {
list.add(s);
}
return list;
}
}
4.3.3 中缀转换成后缀
上述中,中缀表达式为人类更直观能够看明白的表达式,而后缀表达式为机器更为简单计算的表达式,如何完成从中缀表达式向后缀表达式的转换?逆波兰表达式流程:
思路分析:
-
初始化两个栈:运算符栈
stack1
和储存中间结果的栈stack2
; -
定义指针变量,从左至右扫描中缀表达式;
-
当遇到操作数时,将其压数栈
stack2
; -
当遇到运算符时,比较当前运算符与
stack1
栈顶运算符的优先级:- 如果
stack1
为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈; - 否则,若优先级比栈顶运算符的高,也将运算符压入
stack1
; - 否则,将
stack1
栈顶的运算符弹出并压入到stack2
中,再次重复这一步骤,与stack1
中新的栈顶运算符相比较;
- 如果
-
遇到括号时:
- 如果是左括号“(”,则直接压入
stack1
- 如果是右括号“)”,则依次弹出
stack1
栈顶的运算符,并压入stack2
,直到遇到左括号为止,此时将这一对括号丢弃
- 如果是左括号“(”,则直接压入
-
重复步骤2至5,直到指针指向表达式的最右边
-
将
stack1
中剩余的运算符依次弹出并压入stack2
-
依次弹出
stack2
中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
代码实现:
package com.kevin.arrayStack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* @author : kevin ding
* @date : 2022/2/27 15:28
* @description : 中缀表达式转后缀表达式
*/
public class InfixToSuffixExpression {
public static void main(String[] args) {
String expression = "7*2+8/4+(60+(22-5))*3";
// String expression = "1+((2+3)*4)-5";
// 转换成list方便进行遍历
List<String> expressionList = expressionToList(expression);
List<String> suffixExpression = parseToSuffixExpression(expressionList);
System.out.println("后缀表达式为:" + suffixExpression);
int res = PolandExpressionCalculator.polandCalculator(suffixExpression);
System.out.println("表达式的计算结果为:" + res);
}
private static List<String> parseToSuffixExpression(List<String> expressionList) {
// 定义两个栈 stack1用于存放运算符,stack2用于存放中间结果
Stack<String> stack1 = new Stack<>();
List<String> stack2 = new ArrayList<>();
// 遍历expressionList
for (String item : expressionList) {
// 如果当前值是一个数:直接入栈stack2
if(item.matches("\\d+")){
stack2.add(item);
// 如果遇到的是左括号。直接入栈符号栈stack1
}else if(item.equals("(")){
stack1.push(item);
// 如果是右括号,需要将符号栈中的元素弹出并入stack2,直到stack1的栈顶元素为左括号
}else if(item.equals(")")){
while (!stack1.peek().equals("(")){
// 不为左括号,就挨个弹出并加入到stack2中
stack2.add(stack1.pop());
}
// 当前stack1的栈顶元素左括号,直接将其弹出,舍弃
stack1.pop();
// 当前是运算符+ - * /
}else{
while(true){
// 如果符号栈为空,或者栈顶元素为左括号,则当前运算符直接入栈
if(stack1.size() == 0 || stack1.peek().equals("(")){
stack1.push(item);
break;
// 否则 进行优先级的比较 当前元素优先级比较高,就直接入栈,当前元素优先级小于等于栈顶元素,将栈顶元素取出并入栈2,再进行循环
}else{
// 当前优先级小于等于栈顶元素 将stack1中的元素出栈,并加入到stack2中
if(getPriority(item) <= getPriority(stack1.peek())){
stack2.add(stack1.pop());
}else{
stack1.push(item);
break;
}
}
}
}
}
// 遍历结束之后,将stack1中的元素分别弹出 并加入到stack2中
while (stack1.size() != 0){
stack2.add(stack1.pop());
}
return stack2;
}
private static List<String> expressionToList(String expression) {
List<String> list = new ArrayList<>();
// 指定一个指针,开始遍历
int index = 0;
String str; // 用于拼接
do {
// 如果当前元素是一个非数字,加入到list中
if(expression.charAt(index) < 48 || expression.charAt(index) > 57){
list.add(""+ expression.charAt(index));
index += 1;
}else{
// curChar是一个数,需要考虑多位的情况
str = "";
while(index < expression.length() && expression.charAt(index) >= 48 && expression.charAt(index)<=57){
// 拼接
str += expression.charAt(index);
index += 1;
}
list.add(str);
}
}while (index < expression.length());
return list;
}
// 判断给定元素的优先级方法
// 优先级高的 数字越大
public static int getPriority(String operator){
if(operator.equals("*") || operator.equals("/")){
return 1;
}else if(operator.equals("+") || operator.equals("-")){
return 0;
}else{
// 假定 目前操作符只有 + - * /
return -1;
}
}
}
验证:
String expression = "7*2+8/4+(60+(22-5))*3";
// String expression = "1+((2+3)*4)-5";
// 转换成list方便进行遍历
List<String> expressionList = expressionToList(expression);
List<String> suffixExpression = parseToSuffixExpression(expressionList);
System.out.println("后缀表达式为:" + suffixExpression);
int res = PolandExpressionCalculator.polandCalculator(suffixExpression);
System.out.println("表达式的计算结果为:" + res);
最后输出结果: