基本介绍
- 栈的英文为stack,是一个先进后出(FILO)的有序列表。
- 栈时限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表,允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom).
- 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
出栈(Pop)和入栈(Push)的操作图解如下所示
应用场景
- 子程序的调用:在跳往子程序之前,会先将下一个指令的地址存到堆栈中,直到子程序执行完成后将地址取出。
- 处理递归调用:和子程序的调用类似,只是除了存储下一个指令的地址外,也将参数、区域变量等数据存入栈中。
- 表达式的转换【中缀表达式转后缀表达式】与求值。
- 二叉树的遍历
- 图形的深度优先搜索。
数组实现栈
使用数组实现栈的思路分析示意图如下:
代码实现如下:
/***
* @Auther: hs
* @Date: 2020/5/20 12:03
* 使用数组模拟栈
*/
public class ArrayStackDemo {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(4);
String key="";
boolean loop=true;
Scanner scanner = new Scanner(System.in);
while (loop){
System.out.println("show:表示显示栈");
System.out.println("exit:退出程序");
System.out.println("push:表示添加数据到栈(入栈)");
System.out.println("pop:表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key=scanner.next();
switch (key){
case "show":
stack.list();
break;
case "exit":
scanner.close();
loop=false;
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
stack.push(value);
break;
case "pop":
try {
int res=stack.pop();
System.out.printf("出战的数据是 %d \n",res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
break;
}
}
System.out.println("程序退出~~~");
}
}
//定义一个ArrayStack表示栈
class ArrayStack{
private int maxSize; //栈的大小
private int[] stack; //数组,使用它模拟栈,数据放在该数组中
private int top=-1; //栈顶
public ArrayStack(int maxSize){
this.maxSize=maxSize;
stack=new int[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;
}
top++;
stack[top]=value;
}
/**
* 出栈
*/
public int pop(){
if(isEmpty()){
throw new RuntimeException("栈空,没有数据!");
}
int value=stack[top];
top--;
return value;
}
/**
* 显示栈的数据
*/
public void list(){
if (isEmpty()){
System.out.println("栈空,没有数据!");
return;
}
//从栈顶开始显示数据
for(int i=top;i>=0;i--){
System.out.printf("stack[%d]=%d\n",i,stack[i]);
}
}
}
栈实现综合计算器(中缀表达式)
使用栈来实现计算器,思路分析示意图如下:
代码实现如下:
/***
* @Auther: hs
* @Date: 2020/5/23 12:03
* 中缀表达式计算表达式的结果步骤
* 步骤:
* 1.创建两个栈,一个数栈,一个运算符栈
* 2.遍历表达式,如果当前字符是运算符
* 2.1如果此时运算符栈为空,则直接把当前字符压入到运算符栈中
* 2.2如果此时运算符栈不为空,并且当前字符的优先级大于运算符栈顶元素,则直接加入到运算符栈
* 2.3如果此时运算符栈不为空,并且当前字符的优先级小于运算符栈顶元素,则将数栈pop出两个数,
* 符号栈pop出一个运算符,计算,将结果加入到数栈,并将当前的字符加入到运算符栈中
* 3.如果当前字符是数
* 3.1判断当前是否是表达式最后一个字符,如果是,则直接加入到数栈中国
* 3.2如果不是表达式最后一个字符,则往后查看下一个字符是否是数字,如果是数字,则进行拼接,如果不是则将拼接的结果加入到数栈
* 4.依次将数栈pop出两个数,运算符栈pop出一个运算符,进行计算,并将结果加入到数栈,直到数栈中只剩下最终的计算结果
*/
public class Calculator {
public static void main(String[] args) {
//根据前面的思路,完成表达式的运算
String expression="70+2*6-2";
//创建两个栈,数栈、符号栈
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=' ';
String keepNum="";
while (true){
//获取当前遍历的字符
ch=expression.substring(index,index+1).charAt(0);
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=operStack.cal(num1,num2,oper);
numStack.push(res);
operStack.push(ch);
}else {
operStack.push(ch);
}
}else {
operStack.push(ch);
}
}else {
//如果是数,处理多位数的问题
keepNum+=ch;
if(index==expression.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else {
if(operStack.isOper(expression.substring(index+1,index+2).charAt(0))){
numStack.push(Integer.parseInt(keepNum));
keepNum="";
}
}
}
index++;
if(index>=expression.length()){
break;
}
}
while (true){
if(operStack.isEmpty()){
break;
}
num1=numStack.pop();
num2=numStack.pop();
oper=operStack.pop();
res=operStack.cal(num1,num2,oper);
numStack.push(res);
}
System.out.println("计算结果= "+numStack.pop());
}
}
//创建一个栈
class ArrayStack2{
private int maxSize;
private int[] stack;
private int top=-1;
public ArrayStack2(int maxSize){
this.maxSize=maxSize;
this.stack=new int[maxSize];
}
/**
* 查看栈顶元素
*/
public int peek(){
return stack[top];
}
/**
* 判断栈是否满
*/
public boolean isFull(){
return top==maxSize-1;
}
/**
* 判断栈是否为空
*/
public boolean isEmpty(){
return top==-1;
}
/**
* 入栈操作
*/
public void push(int value){
if(isFull()){
System.out.println("栈满,不能添加数据!");
return;
}
top++;
stack[top]=value;
}
/**
* 出栈操作
*/
public int pop(){
if(isEmpty()){
throw new RuntimeException("栈为空,没有数据!");
}
int value=stack[top];
top--;
return value;
}
/**
* 查看栈中所有元素
*/
public void show(){
if (isEmpty()){
System.out.println("栈为空,没有数据!");
return;
}
for(int i=top;i>=0;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;
}
}
逆波兰计算器
完成一个逆波兰计算器,要求完成如下任务:
- 输入一个逆波兰表达式(后缀表达式),使用栈,计算其结果。
- 支持小括号和多位整数。
- 思路分=分析如下:
代码如下:
package com.atguigu.stack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
//先定义给逆波兰表达式
//(30+4)×5-6 => 30 4 + 5 × 6 - => 164
// 4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 60 + 8 2 / +
//测试
//说明为了方便,逆波兰表达式 的数字和符号使用空格隔开
//String suffixExpression = "30 4 + 5 * 6 -";
String suffixExpression = "4 5 * 8 - 60 + 8 2 / +"; // 76
//思路
//1. 先将 "3 4 + 5 × 6 - " => 放到ArrayList中
//2. 将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈 完成计算
List<String> list = getListString(suffixExpression);
System.out.println("rpnList=" + list);
int res = calculate(list);
System.out.println("计算的结果是=" + res);
}
//将一个逆波兰表达式, 依次将数据和运算符 放入到 ArrayList中
public static List<String> getListString(String suffixExpression) {
//将 suffixExpression 分割
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<String>();
for(String ele: split) {
list.add(ele);
}
return list;
}
//完成对逆波兰表达式的运算
/*
* 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,由此得出最终结果
*/
public static int calculate(List<String> ls) {
// 创建给栈, 只需要一个栈即可
Stack<String> stack = new Stack<String>();
// 遍历 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;
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);
}
}
//最后留在stack中的数据是运算结果
return Integer.parseInt(stack.pop());
}
}
中缀表达式转后缀表达式
后缀表达式适合计算式进行运算,但是却不太容易写出来,尤其是表达式很长的情况下,因此我们需要将中缀表达式转为后缀表达式
具体步骤如下:
1.初始化两个栈:运算符栈s1和存储中间结果的栈s2
2.从左至右扫描中缀表达式
3.遇到操作数,将其压入s2
4.遇到运算符时,比较其与s1栈顶运算符的优先级
- 4.1如果s1为空,或者栈顶运算符为左括号"(",则直接将此运算符入栈s1
- 4.2否则,若优先级比栈顶运算符的高,也将其压入栈s1
- 4.3否则,将s1栈顶的运算符弹出压入到s2中,再次转到4.1与s1中新的栈顶元素相比较
5.遇到运算符时:
- 如果时左括号"(",则直接压入s1
- 如果是右括号")",则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
6.重复步骤2至步骤5,直到表达式的最右边
7.将s1中剩下的的运算符依次弹出压入到s2
8.依次弹出s2栈中的元素,结果的逆序即为中缀表达式对应的后缀表达式
举例说明
将中缀表达式"1+((2+3)x4)-5"转换为后缀表达式的过程如下:
代码演示如下:
public class PolandNotation {
public static void main(String[] args) {
//完成将一个中缀表达式转成后缀表达式的功能
//说明
//1. 1+((2+3)×4)-5 => 转成 1 2 3 + 4 × + 5 –
//2. 因为直接对str 进行操作,不方便,因此 先将 "1+((2+3)×4)-5" =》 中缀的表达式对应的List
// 即 "1+((2+3)×4)-5" => ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
//3. 将得到的中缀表达式对应的List => 后缀表达式对应的List
// 即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
String expression = "1+((2+3)*4)-5";//注意表达式
List<String> infixExpressionList = toInfixExpressionList(expression);
System.out.println("中缀表达式对应的List=" + infixExpressionList); // ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
List<String> suffixExpreesionList = parseSuffixExpreesionList(infixExpressionList);
System.out.println("后缀表达式对应的List" + suffixExpreesionList); //ArrayList [1,2,3,+,4,*,+,5,–]
System.out.printf("expression=%d", calculate(suffixExpreesionList)); // ?
/*
//先定义给逆波兰表达式
//(30+4)×5-6 => 30 4 + 5 × 6 - => 164
// 4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 60 + 8 2 / +
//测试
//说明为了方便,逆波兰表达式 的数字和符号使用空格隔开
//String suffixExpression = "30 4 + 5 * 6 -";
String suffixExpression = "4 5 * 8 - 60 + 8 2 / +"; // 76
//思路
//1. 先将 "3 4 + 5 × 6 - " => 放到ArrayList中
//2. 将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈 完成计算
List<String> list = getListString(suffixExpression);
System.out.println("rpnList=" + list);
int res = calculate(list);
System.out.println("计算的结果是=" + res);
*/
}
//即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
//方法:将得到的中缀表达式对应的List => 后缀表达式对应的List
public static List<String> parseSuffixExpreesionList(List<String> ls) {
//定义两个栈
Stack<String> s1 = new Stack<String>(); // 符号栈
//说明:因为s2 这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出
//因此比较麻烦,这里我们就不用 Stack<String> 直接使用 List<String> s2
//Stack<String> s2 = new Stack<String>(); // 储存中间结果的栈s2
List<String> s2 = new ArrayList<String>(); // 储存中间结果的Lists2
//遍历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();//!!! 将 ( 弹出 s1栈, 消除小括号
} else {
//当item的优先级小于等于s1栈顶运算符, 将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与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
}
//方法:将 中缀表达式转成对应的List
// s="1+((2+3)×4)-5";
public static List<String> toInfixExpressionList(String s) {
//定义一个List,存放中缀表达式 对应的内容
List<String> ls = new ArrayList<String>();
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 置成"" '0'[48]->'9'[57]
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;//返回
}
//将一个逆波兰表达式, 依次将数据和运算符 放入到 ArrayList中
public static List<String> getListString(String suffixExpression) {
//将 suffixExpression 分割
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<String>();
for(String ele: split) {
list.add(ele);
}
return list;
}
//完成对逆波兰表达式的运算
/*
* 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,由此得出最终结果
*/
public static int calculate(List<String> ls) {
// 创建给栈, 只需要一个栈即可
Stack<String> stack = new Stack<String>();
// 遍历 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;
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);
}
}
//最后留在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("不存在该运算符" + operation);
break;
}
return result;
}
}