栈,先入后出
先来个栈的手写代码,这里面你要知道有哪些方法,比如jdk自带的Stack的栈也就那几种方法。
package a_heima.stack;
/**
* @ClassName MyStack
* @Description TODO 使用数组表示栈
* @Author zyhh
* @date 2023/12/27 16:50
* @version: 1.0
*/
import java.lang.reflect.Array;
/**
* 特征是:先入后出,变化的一段是栈顶,固定的一端是栈底
*/
public class MyStack {
public static void main(String[] args) {
// 栈测试
ArrayStack arrayStack = new ArrayStack(4);
System.out.println(arrayStack.isEmpty());
System.out.println(arrayStack.isFull());
arrayStack.push(1);
arrayStack.push(2);
arrayStack.push(3);
arrayStack.push(4);
System.out.println("是否栈满::");
System.out.println(arrayStack.isFull());
arrayStack.push(5);
// arrayStack.push(5);
arrayStack.list();
System.out.println("==========分割线==================");
System.out.println("弹出的数据为:"+arrayStack.pop());
arrayStack.list();
}
}
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 (stack.length<=top+1);
}
// 栈空
public boolean isEmpty(){
return top==-1;
}
// 入栈
public void push(int value){
// 先判断栈是否满
boolean full = isFull();
if(full){
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.println(stack[i]);
}
}
}
用中缀表达式表示计算机计算步骤,啥是中缀表达式,这么说吧,咱们学数学的时候的思考步骤和中缀表达式很像。就像先乘除后加减一样。
中缀表达式对于我们人来说好理解,对于计算机来说不好操作。因此,在计算结果时,往往会将中缀表达式转为其他表达式操作(比如转成后缀表达式)。
里面的注解写的比较清楚,常回来看看
package a_heima.stack;
/**
* @ClassName Computer
* @Description TODO 计算器,使用栈表示计算器的结果,两个栈,数栈,符号栈
* @Description TODO 注意这个是中缀表达式
* @Author zyhh
* @date 2023/12/28 16:56
* @version: 1.0
*/
// 首先,通过index来遍历表达式。
// 如果发现index是数,就直接入数栈,如果扫描到的是符号,就分如下情况。
// 1.如果当前符号栈为空,就直接入栈。
// 2。如果符号栈部位空,有操作符,就进行比较,如果当前的操作符的优先级小于或等于栈中的操作符,先不要把当前的入栈,需要从数栈中pop出两个数,在从符号栈这种pop出一个符。
// 将得到的结果入数栈,然后将当前的操作符入符号栈。
// 如果当前符号优先级大于栈中的操作符,就直接1入栈。
// 当表达式扫描完毕后,就顺序的从数栈和符号栈中pop出相应的数据和符号。
// 减法运算时,是下面的数据减去上面的数据。
/**
* 计算器代码实现
*/
public class Computer {
public static void main(String[] args) {
String exp = "70+2*6-4";
// 创建两个栈,数栈,一个符号栈
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=exp.substring(index,index+1).charAt(0);
// 如果ch为符号
if(operStack.isOper(ch)){
// 再判断当前符号栈是否为空,为空则直接入栈
if(operStack.isEmpty()){
operStack.push(ch);
}else {//不为空就判断优先级大小
if(operStack.pro(ch)<=operStack.pro(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 {//如果ch是数
// numStack.push(ch-48);
// 当处理多位数时,不能吧数立即入栈,也可能为多位数,向exp的表达式index再向后移动,是数就扫描,是符号再入栈。
keepNum+=ch;
// 如果ch是exp最后一位了。直接放入栈。
if(index==exp.length()-1){
numStack.push(Integer.valueOf(keepNum));
keepNum="";
}else {
if(operStack.isOper(exp.substring(index+1,index+2).charAt(0))){
numStack.push(Integer.valueOf(keepNum));
//重要!!!!!!!!
keepNum="";
}else {
}
}
}
// index+1
index++;
if(index>=exp.length()){
break;
}
}
// 上面扫描完毕了,符号栈和数栈该进去的也都进去了,就该挨个出来计算了
while (true){
if(operStack.isEmpty()){
break;
}
num1=numStack.pop();
num2=numStack.pop();
oper=operStack.pop();
int cal = numStack.cal(num1, num2, oper);
numStack.push(cal);
}
// 最后数栈最后值就是结果
res=numStack.pop();
System.out.println("最后计算的结果是:::"+res);
}
}
//先创建一个栈
class ArrayStack2{
private int maxSize;
private int[] stack;
private int top=-1;
public ArrayStack2(int maxSize) {
this.maxSize = maxSize;
stack=new int[maxSize];
}
// 栈满
public boolean isFull(){
return (stack.length<=top+1);
}
// 栈空
public boolean isEmpty(){
return top==-1;
}
// 入栈
public void push(int value){
// 先判断栈是否满
boolean full = isFull();
if(full){
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.println(stack[i]);
}
}
// 返回数据的优先级
public int pro(int oper){
if(oper=='*'||oper=='/'){
return 1;
}else if (oper=='+'||oper=='-'){
return 0;
}else {
return -1;//嘉定目前的表达式只有+-*/
}
}
// 把符号栈上一个符号优先级获取到
public int peek(){
return stack[top];
}
// 判断是不是一个运算符
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;
}
return res;
}
}
前缀表达式:(又名波兰表达式)
从右至左扫
描表达式表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对他们做相应的运算,并将结果入栈:重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。
例如:(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,由此的出最终结果
后缀表达式:(又称逆波兰表达式)
听名字就知道和前缀表达式有一部分相似,就是逆了而已,看看是怎么逆的:
后缀表达式就是把前缀表达式运算符位于操作数之后了。
比如:
正常表达式 :逆波兰表达式
a+b :ab+
a+(b-c) :abc-+
a+(b-c)d :abc-d*+
a+d(b-c) :adbc-*+
a=1+3 :a13=
对于上面的逆波兰表达式:你可以了解到,先出现的那个符号必定是优先级最高的。就像那个a+(b-c)是abc-+中-
符号在()里,所以优先级最高,就最先出现。
为了更好的理解,现在用后缀表达式描述求值过程。
从左到右一次扫描,遇到数字时,将数字压入栈,遇到运算符时,弹出栈顶的俩数,用运算符对他们做相应的计算,并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值就是表达式结果。
举例:(3+4)5-6对应后缀:34+56-,其计算步骤为:
1):从左到右扫描,将3和4压入栈
2):遇到+运算符,因此弹出4和3(4为栈顶,3为次顶),计算3+4,得7入栈
3):将5入栈;
4):接下来就是运算,因此弹出5和7,计算出75=35,将35入栈;
5):将6入栈
6):最后是-运算符,计算出35-6的值,即29,这就是最后结果
逆波兰计算器代码:
package a_heima.stack;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* @ClassName NiBoLan
* @Description TODO 逆波兰计算器来计算
* @Description TODO
* @Author zyhh
* @version: 1.0
*/
public class NiBoLan {
public static void main(String[] args) {
// 将算式(3+4)*5-6直接转化为逆波兰表达式。
String exp="3 4 + 5 * 6 -";
//
Stack stack=new Stack();
// 挨个扫描太慢,直接放到List当中
// 思路:
// 1.先放到list。
// 2.有ArrayList传递给一个方法,配合栈完成计算
List<String> list=getListString(exp);
int cal = cal(list);
System.out.println("计算的数值为:"+cal);
}
// 将字符串转化为集合
public static List getListString(String str){
ArrayList list = new ArrayList();
String[] s = str.split(" ");
for (String s1 : s) {
list.add(s1);
}
return list;
}
// 计算
public static int cal(List<String> ls){
// 创建给栈,只需要一个栈
Stack stack = new Stack();
for (String l : ls) {// 遍历list
if(l.matches("\\d+")){//如果匹配的是多位数
stack.push(l);
}else {//否则就是符号,此时就得数据出栈
int num1 = Integer.parseInt(String.valueOf(stack.pop()));
int num2 = Integer.parseInt(String.valueOf(stack.pop()));
int res=0;
if(l.equals("+")){
res= num2+num1;
}else if(l.equals("-")){
res= num2-num1;
}else if(l.equals("*")){
res= num2*num1;
}else if(l.equals("/")){
res= num2/num1;
}else {
throw new RuntimeException("运算有误");
}
// 把res入栈
stack.push(res);
}
}
return Integer.parseInt(String.valueOf(stack.pop()));
}
}
前面我们已经讲到,中缀表达式适合我们人来的计算习惯,但后缀表达式才是适合计算机的计算。那我们如何把中缀转前缀呢?这样的话,我们平时写出来的表达式就可以自动转为后缀来让计算机加快计算。
下面一起来look!
先分析一下步骤:举例:中缀表达式:1+((2+3)*4)-5
先初始化两个栈,s1栈(暂存符号)和s2栈(暂存数据)。
1.从左向右扫描表达式。
2.遇到操作数,压栈到s2栈。
3.遇到符号,比较其与s1栈最上面的优先级。
3.1:如果s1为空,或者栈顶元素符是左括号“(”,或优先级高于栈顶,就直接加入
3.2:否则,将s1栈顶的运算符弹出并压入到s2中,再次转到4.1与s1中新的栈顶运算符相比较
4.遇到括号时:
4.1:如果是左括号“(”,则直接压入s1,
4.2:如果是右括号“)”,则依次弹出s1栈顶运算符,并压入s2,直到遇到左括号为止,此时这一对括号丢弃。
5.重复步骤2至5,直到表达式最右边。
6.将s1中剩余的运算符依次弹出并压入s2
7.依次弹出s2中的元素并逆序输出,结果的逆序即为中缀表达式对应的后缀表达式。
中缀·表达式转后缀表达式代码实现:
package a_heima.stack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* @ClassName Tran
* @Description TODO 中缀表达式转后缀表达式代码实现
* @Author zyhh
* @date 2024/1/2 11:19
* @version: 1.0
*/
public class Tran {
public static void main(String[] args) {
// 1+((2+3)*4)-5==》1 2 3 + 4 * + 5 -
String exp="1+((2+3)*4)-5";
List list = forList(exp);//[1, +, (, (, 2, +, 3, ), *, 4, ), -, 5]
System.out.println(list);
List tran = tran(list);
System.out.println("后缀表达式为:::"+tran);
}
/**
* TODO 把字符串加到集合中。这里也考虑到了多位数字的情况
* @param str
* @return
*/
public static List forList(String str){
List list = new ArrayList();
int i=0;
String str2 = "";//多位数拼接
char cc;//每遍历到一个字符,就放入c
do{
if((cc=str.charAt(i))<48||(cc=str.charAt(i))>57){//如果cc是非数字就加入到list。
list.add(""+cc);
i++;
}else {
str2="";
while((i< str.length()) && (cc=str.charAt(i))>=48 && (cc=str.charAt(i))<=57){
str2 += cc;
i++;
}
list.add(str2);
}
}while(i<str.length());
return list;
}
/**
* 中缀转后缀
* @param ls
*/
public static List<String> tran(List<String> ls) {
// 定义一个栈一个list集合。由于s2可以明显看出在整个在转换过程中没有pop操作,最后又需要逆序输出,list功能已经够了。
Stack<String> s1 = new Stack();//符号栈
List<String> s2 = new ArrayList();//存储中间结果
// 遍历ls
for (String item : ls) {
if (item.matches("\\d+")) {//如果这个元素是个数,就进集合
s2.add(item);
} else if (item.equals("(")) {//如果是左括号,则进符号栈
s1.push(item);
} else if (item.equals(")")) {//如果是右括号,就一次弹出符号进s2
while (!s1.peek().equals("(")) {
s2.add(s1.pop());
}
s1.pop();//将左括号(弹出s1,来消除小括号
} else {
// 当item优先级小于或等于栈顶运算符,就把栈顶的弹出加入s2
while (s1.size() != 0 && com(item) <= com(s1.peek())) {
s2.add(s1.pop());
}
s1.push(item);//还要把优先级小的放进去
}
}
// 将剩余的加入栈中
while (s1.size() != 0) {
s2.add(s1.pop());
}
return s2;
}
// 比较优先级高低的方法
public static int com(String a1){
int a=0;
if(a1.equals("*")||a1.equals("/")){
a= 2;
}else if(a1.equals("+")||a1.equals("-")){
a= 1;
}else {
System.out.println("运算符异常,请检查");
}
return a;
}
}
============================== end========================================