栈
1.基本介绍
- 栈(stack)是一个 先入后出(FILO-First In Last Out) 的有序列表。
- 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
- 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
2.栈的应用场景
- 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
- 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
- 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
- 二叉树的遍历。
- 图形的深度优先(depth一first)搜索法
3.入栈和出栈的概念
4.栈快速入门
- 使用数组来模拟栈思路分析:
- 定义一个 top 来表示栈顶,初始化 为 -1
- 入栈的操作,当有数据加入到栈时,top++; stack[top] = data;
- 出栈的操作, int value = stack[top]; top–; return value;
代码:
import java.util.Scanner;
//数组模拟栈
public class ArrayStackDemo {
public static void main(String[] args) {
//测试ArrayStack
ArrayStack arrayStack = new ArrayStack(4);
String key = "";
boolean loop = true; //控制菜单是否退出
Scanner scanner = new Scanner(System.in);
while (loop){
System.out.println("1.show:显示栈的内容");
System.out.println("2.exit:退出程序");
System.out.println("3.push:入栈");
System.out.println("4.pop:出栈");
System.out.println("请输入你的选择:");
key = scanner.next();
switch (key){
case "1":
arrayStack.list();
break;
case "3":
System.out.println("请输入一个数:");
int value = scanner.nextInt();
arrayStack.push(value);
break;
case "4":
try {
int res = arrayStack.pop();
System.out.println("出栈的元素是:" + res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "2":
scanner.close();
loop = false;
break;
}
}
System.out.println("程序运行结束!");
}
}
//定义一个用数组模拟的栈
class ArrayStack{
private int maxSize;//栈的大小
private int[] stack;//模拟栈的数组,用于存放数据
private int top = -1;//指向栈顶,初始化为-1
//构造器
public ArrayStack(int maxSize){
this.maxSize = maxSize;
stack = new int[this.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;
}
top++;
stack[top] = value;
}
//出栈-pop 将栈顶的元素返回
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.println("stack[" + i + "] = " + stack[i]);
}
}
}
5.前缀,中缀,后缀表达式
- 前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前。举例说明: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6
前缀表达式的计算机求值:
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素, 栈顶元素在操作符左边),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
例如: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:
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,由此得出最终结果
- 中缀表达式就是常见的运算表达式,如(3+4)×5-6。中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作,因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式.)
- 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后,举例说明: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –
后缀表达式的计算机求值:从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素,次顶元素在操作符左边),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 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,由此得出最终结果
- 中缀表达式转后缀表达式:后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转成后缀表达式。
思路分析(主要要区分当前的字符是操作数还是运算符还是括号):
1)初始化两个栈:运算符栈s1和储存中间结果的栈s2(因为s2不需要pop操作,所以也可以将栈转化为ArrayList,这样输出表达式时不需要逆序输出了);
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中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
例子:将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式的过程如下
因此结果为
“1 2 3 + 4 × + 5 –”
6.计算器:
输入一个中缀表达式,然后将其转为逆波兰表达式(后缀表达式),使用栈(Stack), 计算表达式的结果,这里支持对整数的计算。
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
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,–]
//1+((-2+3)*4)-5 会报错的 没处理负数
String expression = "1+((2+3)*4)-5";//注意表达式
List<String> infixExpressionList = toInfixExpressionList(expression);
System.out.println("中缀表达式对应的List = " + infixExpressionList);
List<String> suffixExpreesionList = parseSuffixExpreesionList(infixExpressionList);
System.out.println("后缀表达式对应的List" + suffixExpreesionList); //ArrayList [1,2,3,+,4,*,+,5,–]
System.out.printf("%s = %d\n", expression, calculate(suffixExpreesionList)); // 16
/*
//先定义给逆波兰表达式
//(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(list);
int res = calculate(list);
System.out.println("计算出的结果是: " + res);
*/
}
//将中缀表达式的list转成后缀表达式的list
//ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] => ArrayList [1,2,3,+,4,*,+,5,–]
public static List<String> parseSuffixExpreesionList(List<String> list){
//定义两个栈
Stack<String> s1 = new Stack<>(); //符号栈
//因为在整个转换过程中,s2没有pop的操作,而且如果是栈的话,在最后还需要逆序输出,
//比较麻烦,所以在这里直接使用一个list
// Stack<String> s2 = new Stack<>();
ArrayList<String> s2 = new ArrayList<>(); //储存中间结果
//遍历list
for (String item : list) {
if(item.matches("\\d+")){ //如果是一个数字,则直接加入s2
s2.add(item);
}else if("(".equals(item)){ //如果当前符号是 ( 则直接入栈
s1.push(item);
}else if(")".equals(item)){ //如果当前符号是 ) 则依次弹出符号栈栈顶的元素,并添加到s2,直到遇到 ) 为止,并将这一对括号扔掉
while (!"(".equals(s1.peek())){
s2.add(s1.pop());
}
s1.pop(); //栈顶的 ( 出栈
}else if(s1.isEmpty()){ //如果s1为空,则直接入栈
s1.push(item);
}else if("(".equals(s1.peek())){ //符号栈栈顶元素是 ( 则直接入栈
s1.push(item);
}else{ //当前操作符的优先级 小于或等于 s1栈顶元素的优先级时,将栈顶元素运算符弹出,并加入到s2中,然后转4.1与新的栈顶元素
//运算符相比较
while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){
s2.add(s1.pop());
}
//在将item压入s1栈中
s1.push(item);
}
}
//将s1中剩余的元素依次弹出,加入到s2中
while (s1.size() != 0){
s2.add(s1.pop());
}
//因为存放的一个ArrayList,所以逆波兰表达式就是遍历list,而不需要逆序输出的
return s2;
}
//中缀表达式转成对应的List
// s="1+((2+3)×4)-5";
public static List<String> toInfixExpressionList(String s){
//定义一个List,存放中缀表达式str转换为List的结果
List<String> list = new ArrayList<>();
int i = 0; //用于遍历中缀表达式的
String str = "" ; //用于对多位是数的拼接
char c = ' '; //每遍历一个字符就放到c
while(i <s.length()){
//如果是一个非数字则直接添加进list中 '0'[48]->'9'[57]
if((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57){
list.add("" + c);
i++;
}else {
//如果是一个数字的话,则需要考虑是不是一个多位数
str = "";
while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57){
str += c;
i++;
}
list.add(str);
}
}
return list;
}
//将一个逆波兰表达式中的数字和运算符 放入到ArrayList中
public static List<String> getListString(String suffixExpression){
//根据空格切割表达式
String[] split = suffixExpression.split(" ");
ArrayList<String> list = new ArrayList<>();
for (String s : split) {
list.add(s);
}
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出两个数,计算之后的结果在入栈 是 后pop出的书 (+-*/) 先pop出的数
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
if("+".equals(item)){
res = num1 + num2;
}else if("-".equals(item)){
res = num1 - num2;
}else if("*".equals(item)){
res = num1 * num2;
}else if("/".equals(item)){
res = num1 / num2;
}else {
throw new RuntimeException("操作符异常");
}
//res入栈
stack.push("" + res);
}
}
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;
}
}
计算结果: