1 栈的简介
栈(stack)
栈是一个先入后出(FILO-first in last out)的有序列表。
栈是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表,允许插入和删除的一端,为变化的一端,称为栈顶(top),另一端为固定的一端,称为栈低(bottom)
根据栈的定义可知,最先放入栈中元素在栈低,最后放入元素宅栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
应用场景
- 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆中,直到子程序执行完后再将地址取出,以回到原来的程序中
- 处理递归调用:和子程序调用类似,只有除了存储下一指令的地址外,也将参数、区域变量等数据存入堆栈中
- 表达式的转换【中缀表达式转后缀表达式】与求值(实际解决)
- 二叉树的遍历
- 图形的深度优先(depth-first)搜索法
1.1 图解数组模拟栈
用数组实现栈的思路分析:
定义一个top来表示栈顶,初始化为-1;
入栈操作,当有数据加入到栈时,top++;stack[top]=data;
出栈操作,int value =stack[top];top–,return value;
1.2 代码实现
public class ArrayStack {
//栈的大小
private int maxSize;
//数组模拟栈 将数据放入数组
private Object[] stack;
//栈顶 初始化为-1
private int top = -1;
public ArrayStack (int maxSize){
this.maxSize = maxSize;
stack = new Object[this.maxSize];
}
public boolean isFull(){
return top == maxSize-1;
}
public boolean isEmpty(){
return top == -1;
}
//入栈
public void push(Object obj){
if(isFull()){
System.out.println("栈已经满了~~~");
return;
}
top ++;
stack[top] = obj;
}
//出栈
public Object pop(){
if(isEmpty()){
throw new RuntimeException("栈已经空了");
}
Object obj = stack[top];
top --;
return obj;
}
//遍历
public void list(){
if(isEmpty()){
System.out.println("空栈~~~");
return;
}
for(int i=top;i>=0;i--){
System.out.println("stack["+i+"] = "+stack[i]);
}
}
}
测试代码
public class Test {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(4);
Scanner scanner = new Scanner(System.in);
boolean loop = true;
System.out.println("list:显示元素");
System.out.println("push:添加数据");
System.out.println("pop:获取数据");
System.out.println("exit:退出程序");
String index = "";
while (loop){
index = scanner.next();
switch (index){
case "list":
stack.list();
break;
case "push":
System.out.println("请输入:");
String st = scanner.next();
stack.push(st);
break;
case "pop":
Object pop = stack.pop();
System.out.println("obj="+pop);
break;
case "exit":
scanner.close();
loop = false;
break;
}
}
System.out.println("over~~~");
}
}
测试打印
2 栈 完成计算机表达式的计算思路
1 创建两个栈,一个数栈,一个运算符栈
2 通过一个index值(索引),来遍历我们的表达式
3 当发现是一个数字时直接入数栈
4 当为运算符号时需要分情况而定
- 如果发现当前运算符栈为空直接入栈
- 如果发现有运算符;若当前运算符优先级小于或者等于栈中的运算符,就从数栈pop出两个数在从运算符中pop出一个运算符进行运算,将计算结果入数栈,将当前运算符入运算符栈;若当前运算符的优先级大于栈中运算符,直接入符号栈
5 当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号进行计算
6 最后在数栈只有一个数字就是表达式的结果
2.2 代码实现
不带括号
栈添加两个几个方法
public class ArrayStack2 {
//栈的大小
private int maxSize;
//数组模拟栈 将数据放入数组
private int[] stack;
//栈顶 初始化为-1
private int top = -1;
public ArrayStack2(int maxSize){
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
public boolean isFull(){
return top == maxSize-1;
}
public boolean isEmpty(){
return top == -1;
}
//入栈
public void push(int obj){
if(isFull()){
System.out.println("栈已经满了~~~");
return;
}
top ++;
stack[top] = obj;
}
//出栈
public int pop(){
if(isEmpty()){
throw new RuntimeException("栈已经空了");
}
int obj = stack[top];
top --;
return obj;
}
//遍历
public void list(){
if(isEmpty()){
System.out.println("空栈~~~");
return;
}
for(int i=top;i>=0;i--){
System.out.println("stack["+i+"] = "+stack[i]);
}
}
//查看栈顶元素
public int peak(){
if(top == -1){
System.out.println("空栈~~~");
return -1;
}
return stack[top];
}
//运算符优先级 只计算加减乘除
public int priority(int operate){
if(operate == '*'||operate=='/'){
return 1;
}else if(operate=='-'||operate=='+'){
return 0;
}else{
return -1;
}
}
//判断的是否是运算符
public boolean isOperate(char value){
return value=='+'||value=='-'||value=='*'||value=='/';
}
//计算
public int cal(int num1,int num2,int operate){
int value = 0;
switch (operate){
case '+':
value = num2+num1;
break;
case '-':
value = num2-num1;
break;
case '*':
value = num2*num1;
break;
case '/':
value = num2/num1;
break;
}
return value;
}
}
测试验证代码
public class Calculator {
public static void main(String[] args) {
String expression = "11+11*7+2*7-11";
//创建两个栈,一个数栈,一个运算符栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operateStack = new ArrayStack2(10);
//通过一个index值(索引),来遍历我们的表达式
int index = 0;
String keepNum = "";//拼接多位数
while(true){
char c = expression.substring(index, index + 1).charAt(0);
if(operateStack.isOperate(c)){//如果是运算符
if(operateStack.isEmpty()){//如果发现当前运算符栈为空直接入栈
operateStack.push(c);
}else{//非空时
if(operateStack.priority(c)>operateStack.priority(operateStack.peak())){ //若当前运算符的优先级大于栈中运算符,直接入符号栈
operateStack.push(c);
}else{ //若当前运算符优先级小于或者等于栈中的运算符,
//从数栈pop出两个数在从运算符中pop出一个运算符进行运算,将计算结果入数栈,将当前运算符入运算符栈;
int num1 = numStack.pop();
int num2 = numStack.pop();
int operate = operateStack.pop();
int value = numStack.cal(num1, num2, operate);
numStack.push(value);
operateStack.push(c);
}
}
}else{//当发现是一个数字时直接入数栈
//numStack.push(c-48);//这里是字符 不是真正的数字 当为多位数时 计算会出错
keepNum += c;
if(index == expression.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else{
//判断下一个字符是不是数字 是就继续扫描 ,如果是运算符就则入栈
//只是看后一位 不是index++;如果最后一位是运算符 则数字入栈 并且记得清空keepNum
if(operateStack.isOperate(expression.substring(index+1,index+2).charAt(0))){
numStack.push(Integer.parseInt(keepNum));
keepNum="";
}
}
}
index++;
if(index>=expression.length()){
break;
}
}
//当表达式扫描完毕 就顺序的从数栈和符号栈中pop出相应的数和符号进行计算
while(true){
if(operateStack.isEmpty()){
break;
}
int num1 = numStack.pop();
int num2 = numStack.pop();
int operate = operateStack.pop();
int value = numStack.cal(num1, num2, operate);
numStack.push(value);
}
System.out.println("最终表达式"+expression+"="+numStack.pop());
}
}
信息打印
3 前、中、后缀表达式
前缀表达式又称波兰表达式,前缀表达式的运算符位于操作数之前
后缀表达式又称逆波兰表达式
前缀表达式的计算求值
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对他们的相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程指导表达式最左端,最后运算得出的值即为表达式的结果
举例说明:(3+4)x5-6 对应的前缀表达式是 - x + 3 4 5 6,针对前缀表达式求值步骤如下:
- 从右至左扫描,将6 5 4 3 压入堆栈
- 遇到“+”运算符 因此弹出3和4 (3为栈顶元素,4为次顶元素),计算出3+4的值,在将结果7 入栈;
- 接下来是“x”运算符,因此弹出7和5,计算出7x5=35,将35入栈
- 最后的运算符“-”计算出35-6的值即29,由此得出结果
中缀表达式
中缀表达式就是常见的运算表达式,如(3+4)x5-6
中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看见这个问题),因此,在计算结果时,往往将中缀表达式转成其它表达式来操作(一般为后缀表达式)
后缀表达式的计算求值
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对他们的相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程指导表达式最左端,最后运算得出的值即为表达式的结果
举例说明:(3+4)x5-6 对应的后缀表达式是 3 4 + 5 x 6 -,针对后缀表达式求值步骤如下:
- 从左至右扫描,将3 4 压入堆栈
- 遇到“+”运算符 因此弹出3和4 (3为栈顶元素,4为次顶元素),计算出3+4的值,在将结果7 入栈;
- 将5入栈
- 接下来是“x”运算符,因此弹出7和5,计算出7x5=35,将35入栈
- 将6入栈
- 最后的运算符“-”计算出35-6的值即29,由此得出结果
3.1 逆波兰计算器
我们完成一个逆波兰计算器
1 输入一个逆波兰表达式,使用栈计算期结果
2 支持小括号和多位数整数
代码实现
public class PolandNotation {
public static void main(String[] args) {
//定义一个波兰表达式(3+4)*5-6 => 3 4 + 5 * 6 -
String suffixExpression = "30 4 + 5 * 61 -";
//将后缀表达式放入list中
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<>(Arrays.asList(split));
System.out.println("返回的list:"+list);
int num = calculator(list);
System.out.println("表达式计算结果为:"+suffixExpression+" = "+num);
}
// 完成对逆波兰表达式的运算
// 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,由此得出最终结果
private static int calculator(List<String> list) {
Stack<String> stack = new Stack<>();
for(String s:list){
//用正则匹配数组
if(s.matches("\\d+")){//数字入栈
stack.push(s);
}else{//运算符计算
//弹出两个数字和当前的运算符元素
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
int cal = cal(num1, num2, s);
stack.push(cal+"");
}
}
return Integer.parseInt(stack.pop());
}
public static int cal(int num1,int num2,String operate){
int value = 0;
switch (operate){
case "+":
value = num2+num1;
break;
case "-":
value = num2-num1;
break;
case "*":
value = num2*num1;
break;
case "/":
value = num2/num1;
break;
}
return value;
}
}
打印信息
3.2 中缀表达式转后缀表达式
步骤如下:
- 1)初始化两个栈运算符s1和存储中间结果的栈s2;
- 2)从左至右扫描中缀表达式
- 3)遇到操作数时,将其压s2 (数字入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”转换为后缀表达式的过程如下:
扫描到的元素 | s2(栈底->栈顶) | s1(栈底->栈顶) | 说明 |
---|---|---|---|
1 | 1 | 空 | 数字,直接入s2 |
+ | 1 | + | s1为空,直接入s1 |
( | 1 | +( | 左括号,直接入s1 |
( | 1 | +(( | 左括号,直接入s1 |
2 | 1 2 | +(( | 数字,直接入s2 |
+ | 1 2 | +(( + | s1栈顶为左括号,直接压入 |
3 | 1 2 3 | +(( + | 数字,直接入s2 |
) | 1 2 3 + | +( | 右括号,弹出s1中运算符压入s2知道遇到左括号 |
* | 1 2 3 + | +( * | s1栈顶为左括号,直接压入 |
4 | 1 2 3 + 4 | +( * | 数字,直接入s2 |
) | 1 2 3 + 4 * | + | 右括号,弹出s1中运算符压入s2知道遇到左括号 |
- | 1 2 3 + 4 * + | - | 比较优先级相等,弹出s1栈顶 + 压入s2,当前运算符 - 入s1 |
5 | 1 2 3 + 4 * + 5 | - | 数字,直接入s2 |
扫描完毕 | 1 2 3 + 4 * + 5 - | 空 | s1中剩余的运算符弹出并压入s2 |
最后:依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
因此结果为: “1 2 3 + 4 * + 5 -”
代码示例
将中缀表达转换为后缀表达式并计算结果
public class PolandNotation {
public static void main(String[] args) {
//中缀表达式转后缀表达式 1+((2+3)*4)-5 => 1 2 3 + 4 * + 5 -
//1 将中缀表达式先放入list中方便后续取值
String expression = "11+((21+3)*4)-5";
List<String> InfixExpression = toInfixExpression(expression);
System.out.println("转换后的中缀表达式:"+InfixExpression);
//2 中缀表达式的list => 后缀表达式的List
List<String> suffixExpression = InfixExpression2SuffixExpression(InfixExpression);
System.out.println("返回的list:"+suffixExpression);
//3 计算后缀表达式值
int num = calculator(suffixExpression);
System.out.println("表达式计算结果为:"+suffixExpression+" = "+num);
}
//中缀转换为后缀表达式
private static List<String> InfixExpression2SuffixExpression(List<String> infixExpression) {
//定义两个栈 因为s2中并没有弹出栈的操作 最后还要逆序的数据才是后缀表达式所有用list来接收
Stack<String> s1 = new Stack<>();
List<String> s2 = new ArrayList<>();
for(String item:infixExpression){
if(item.matches("\\d+")){//数字,直接入s2
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.pop();//弹出右括号
}else{//遇到运算符时 比较其与s1栈顶运算符的优先级
//当item的优先级 <= s1栈顶的运算符的优先级 将 s1 栈顶的运算符弹出并加入到 s2 中,再次转到(4.1)与 s1 中新的栈顶运算符相比较
while(s1.size()!=0&&Operation.getValue(item)<=Operation.getValue(s1.peek())){
s2.add(s1.pop());
}
s1.push(item);//若优先级比栈顶的运算符高,也将运算符压入s1
}
}
//将s1中剩余的运算符依次弹出并压入s2
while(s1.size()!=0){
s2.add(s1.pop());
}
return s2;
}
//中缀表达式转换为list
private static List<String> toInfixExpression(String expression) {
List<String> list = new ArrayList<>();
int i = 0;
String s;//多位数拼接
char c;//元素放入char中
do{
//如果是运算符,直接加入的list中
if((c=expression.charAt(i))<48||(c=expression.charAt(i))>57){
list.add(c+"");
i++;
}else{//数字需要拼接
s = "";
while (i<expression.length()&&(c=expression.charAt(i))>=48&&(c=expression.charAt(i))<=57){
s+=c;
i++;
}
list.add(s);
}
}while (i<expression.length());
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,由此得出最终结果
private static int calculator(List<String> list) {
Stack<String> stack = new Stack<>();
for(String s:list){
//用正则匹配数组
if(s.matches("\\d+")){//数字入栈
stack.push(s);
}else{//运算符计算
//弹出两个数字和当前的运算符元素
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
int cal = cal(num1, num2, s);
stack.push(cal+"");
}
}
return Integer.parseInt(stack.pop());
}
public static int cal(int num1,int num2,String operate){
int value = 0;
switch (operate){
case "+":
value = num2+num1;
break;
case "-":
value = num2-num1;
break;
case "*":
value = num2*num1;
break;
case "/":
value = num2/num1;
break;
}
return value;
}
}
//比较运算符优先权
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;
}
}
信息打印