目录
- 栈(Stack)是一个先入后出的有序列表
- 栈是一种限制线性表中元素的插入和删除,只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)
- 根据栈的定义可知,最先放入栈中的元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
1.栈的模拟
使用数组模拟栈:(也可以使用单链表来表示 )
注:在数据入栈和出栈时,必须先判断栈满或栈空!
//判断栈满
public boolean isFull(){
return top == maxSize-1;
}
//判断栈空
public boolean isEmpty(){
return top==-1;
}
1)Top表示栈顶,初始化为-1
class ArrayStack{
private int maxSize; //栈的大小
private int top = -1; //栈顶,无数据时初始化为-1
private int[] stack; //数组模拟栈,数据放在该数组中
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
}
2)数据入栈时,Top++,stack[Top]
//入栈操作
public void push(int value){
if(isFull()){
System.out.println("栈满!");
return;
}
top++;
stack[top] = value;
}
3)数据出栈时,int value = stack[Top] ; Top-- ; return value
//出栈操作
public int pop(){
if(isEmpty()){
throw new RuntimeException("栈空!"); //根据方法结构,此处必须要有返回值,但栈空时,没有数据要返回,所以此处吃用抛出异常来提示栈空!
}
int value = stack[top];
top--;
return value;
}
4)遍历栈
//遍历栈
public void List(){
if(isEmpty()){
System.out.println("栈空!");
return;
}
for (int i = top; i >= 0; i--) {
System.out.println("stack[" + i + "] = " + stack[i]);
}
}
2.使用栈完成表达式的计算
验证:3+2*6-2 = ?
1) 创建一个类,表示栈结构(在栈的模拟中创建的类中添加几个可以用到的方法)
a.返回运算符的优先级
public int priority(int oper){
if(oper == '*' || oper == '/'){
return 1; //设置优先级为1
}else if(oper == '+' || oper == '-'){
return 0;
}else {
return -1; //目前只含有 + - * /
}
}
b.判断是否为运算符(or 数字)
public boolean isOper(char val){
return val == '+' || val == '-' || val == '*' || val == '*';
}
c.数值之间的计算
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;
}
d.查看栈顶数据(主要使用在运算符栈中,需要判断比较扫描道德运算符与栈顶的运算符的优先级),不是出栈
public int showTop(){
return stack[top];
}
2)创建两个栈,并定义需要的变量
//根据思路分析,完成表达式的运算
String expression = "3+3*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 = ' '; //将每次扫面得到的char保存到ch中
3)使用 index 指针扫描 expression,并进行逻辑判断
思路:
a.通过一个index值(索引),遍历表达式
b.如果是数字,就直接入数栈
c.如果是符号,就分情况
i.如果发现当前符号栈为空,就直接入栈(防止栈中符号重复)
ii.如果符号栈有操作符,则进行比较,若当前操作符的优先级小于或等于栈中的操作符,就需要从数栈中pop出两个数,再从符号栈中pop出一个符号,进行运算后,再将得到的结果入数栈,再将当前的操作符入符号栈;若当前操作符的优先级大于栈中的操作符,就直接入符号栈
while(true){
//一次得到 expression 中的每一个字符
ch = expression.substring(index,index+1).charAt(0); //1.每次从字符串中取一个ch组成一个新的字符数组,后读取(因为只取出一个元素,所以新数组中只有一个
//判断是数字还是运算符
if(operStack.isOper(ch)){ //2.1如果是运算符,则需要判断栈中是否为空,若不为空则需要判断与已有运算符的优先级
if(!operStack.isEmpty()) { //3.1判断运算符栈是否为空
//4.判断与已有运算符的优先级
if (operStack.priority(ch) <= operStack.priority(operStack.showTop())) { //4.1若后入运算符优先级小于或等于前入运算
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = operStack.cal(num1, num2 , oper); //5.再将运算后的值,push进数栈
numStack.push(res); // 6.再将目前扫描的运算如入操作符栈
operStack.push(ch);
} else { //4.2若当前操作符的优先级大于栈中的操作符,直接入符号栈
operStack.push(ch);
}
}else { //3.2若运算符栈为空
operStack.push(ch);
}
}else { //2.2 若不是运算符(则是数字)
numStack.push(ch - 48); //直接存存的是ASCII码,数字的ASCII码是从49开始,所以想直接存数字,则需要减去48
}
//让 index + 1 ,并判断是否扫描到 expression 的最后
index++;
if(index >= expression.length()){
break;
}
}
4)扫描完毕过后,就顺序的从数栈和符号栈中pop出响应的符号并运算。最后,数栈中只有一个数字(既表达式结果)
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("表达式: " + expression + " = " + numStack.pop());
此代码中没有加入“()”、多位数等细节问题,有待优化
5)多位数计算
只需在代码步骤2.2入数栈时,需要像 expression 的表达式的 index 后再看一位,如果是数,则继续向后扫描,直至不是数字为止。因此需要定义一个变量(字符串),用于拼接(keepNum)
keepNum += ch;
//判断下一位字符是否为数字,如果是数字则继续扫描
//只是看一位,并不是idex后移
//若 ch 已经是 expression 的最后一位,则直接入栈
if(index == expression.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else if(operStack.isOper(expression.substring(index+1,index+2).charAt(0))){ //表达式在idex指向和idex后一位截取,后取新数组的第一个元素,看是否是运算符
//如果后一位是运算符,则入栈(keepNum = '1' 或者 "123",是字符串,需要转为int类型入数栈)
numStack.push(Integer.parseInt(keepNum));
//重要!!!在keepNum存入栈之后,要进行清空操作!否则下一次拼接会再次基础上继续拼接!
keepNum = "";
}
3.前缀、中缀、后缀
详见: