赞
踩
定义:栈是只允许在一端插入或删除的特定线性表。
栈的应用场景:
1、子程序的调用:在跳往子程序前,会将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2、处理递归调用:和子程序调用类似,除了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
3、 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)—使用操作数栈和运算符栈来完成
4、二叉树的遍历—行遍历
5、图形的深度优先(depth—first)搜索法。
思路分析:
① 定义一个top来表示栈顶,初始化为-1
② 入栈的操作,当有数据加入到栈时,top++;stack[top]=data;
③ 出战的操作,int value =stack[top]; top–; return value;
定义一个ArrayStack类
代码如下:
// 定义一个ArrayStack表示栈 class ArrayStack{ private int maxSize; // 栈的大小 private int[] stack; // 数组模拟栈,数据就放在该数组 private int top =-1; //top表示栈顶,初始化为-1 // 构造器 public ArrayStack(int maxSize) { this.maxSize = maxSize; stack = new int[this.maxSize]; //初始化数组, // 并赋值给数组类型的变量stack } // 栈满 public boolean isFull(){ return top==maxSize-1; } // 栈空 public boolean isEmpty(){ return top==-1; } //入栈 push public void stackPush(int value){ if (top==maxSize-1) { System.out.println("栈满---不能再添加"); return; } top++; stack[top]=value; } // 出栈 pop,将栈顶的数据返回 public int stackPop(){ if (top==-1){ // 抛出异常 throw new RuntimeException("栈空,没有数据---"); } int temp=stack[top]; // 记录下要出栈的值, top--; // 如果直接return,就不能完成top--了 return temp; } // 遍历栈 从栈顶开始到栈底 public void showStack(){ if (top==-1){ System.out.println("栈空,没有数据---"); return; } for (int i=top;i>=0;i--){ System.out.printf("stack[%d]=%d\n",i,stack[i]); } } }
测试如下:
public class ArrayStackDemo { public static void main(String[] args) { // 先创建一个ArrayStack对象---表示栈 ArrayStack arrayStack = new ArrayStack(3); 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": arrayStack.showStack(); break; case "push": System.out.println("请输入你要添加的数:"); int value=scanner.nextInt(); arrayStack.stackPush(value); break; case "pop": // 在里面抛出了异常,可能有异常,try-catch try { int res=arrayStack.stackPop(); System.out.printf("出栈的数据为%d\n",res); }catch (Exception e){ System.out.println(e.getMessage()); } break; case "exit": scanner.close(); loop=false; break; default: break; } } System.out.println("程序退出"); } }
思路分析:
1、先创建个结点类,然后创建单向链表类,之后利用头结点,进行头插,完成进栈操作。
2、由于栈是先进后出,因此出栈也是头结点指向的那个结点。
3、遍历也是从链表头到尾完成。
创建结点类
代码如下:
// 创建一个Person结点类 class Person{ public int num; public Person next; @Override public String toString() { return "Person{" + "num=" + num + '}'; } public Person(int num) { this.num = num; } }
创建栈(单向链表头插)
代码如下:
class ListStack{ // 先创建一个头结点 Person head =new Person(0); public void StackAdd(Person person){ // 添加操作 进栈 person.next = head.next; head.next = person; } public Person StackDel(){ // 删除操作 出栈 if (head.next==null){ System.out.println("栈为空"); return null; }else if (head.next.next==null){ return head.next; }else { Person temp=head.next; // 变量向保存要出栈的数据,以便return head.next=head.next.next; return temp; } } public void StackShow(){ // 显示操作 由于是头插,直接头部遍历 Person temp=head; // 遍历不能破坏链表 要移动辅助遍历temp if (temp.next==null){ return; } while (true){ if (temp.next==null){ break; } System.out.println(temp.next); temp=temp.next; } } }
测试如下:
public class ListStackDemo { public static void main(String[] args) { // 创建结点 Person a = new Person(1); Person b = new Person(2); Person c = new Person(3); Person d = new Person(4); // 创建栈 ListStack listStack = new ListStack(); //添加数据 进栈 listStack.StackAdd(a); listStack.StackAdd(b); listStack.StackAdd(c); listStack.StackAdd(d); System.out.println("进栈后的栈是---"); //显示 listStack.StackShow(); // 删除数据 出栈 Person deldata1=listStack.StackDel(); System.out.println("出栈的元素是"+ deldata1); System.out.println("现在的栈是---"); listStack.StackShow(); } }
1、通过一个index索引,来遍历表达式
2、如果发现是个数字,就直接入数栈
3、如果发现是个符号,就分如下情况
① 如果当前符号栈为空,就直接入栈
②遇到界限符。遇到“( ”直接入符号栈;遇到“)”则依次弹出栈内运算符,并弹出数栈两个数,进行计算,在保存在数栈中。直到弹出“(”为止。
③ 如果符号栈有操作符,就进行比较,如果当前操作符的优先级小于或等于栈中的操作符,就需要从数栈中pop处两个数,再从符号栈中pop出一个符号,进行运算,将得到的结果,入数栈,然后将当前的操作符入符号栈。
4、当表达式扫描完毕,就顺序的从数栈和符号栈中pop处相应的数和符号,并计算,
5、最后在数栈只有一个数字,就是表达式的结果
期间每弹出一个运算符,就需要在弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈。
创建栈并增加(判定优先级、判定运算符、计算)等方法
// 先创建一个栈 // 定义一个ArrayStack2表示栈 class ArrayStack2{ private int maxSize; // 栈的大小 private int[] stack; // 数组模拟栈,数据就放在该数组 private int top =-1; //top表示栈顶,初始化为-1 // 构造器 public ArrayStack2(int maxSize) { this.maxSize = maxSize; stack = new int[this.maxSize]; //初始化数组, // 并赋值给数组类型的变量stack } // 栈满 public boolean isFull(){ return top==maxSize-1; } // 栈空 public boolean isEmpty(){ return top==-1; } //入栈 push public void stackPush(int value){ if (top==maxSize-1) { System.out.println("栈满---不能再添加"); return; } top++; stack[top]=value; } // 出栈 pop,将栈顶的数据返回 public int stackPop(){ if (top==-1){ // 抛出异常 throw new RuntimeException("栈空,没有数据---"); } int temp=stack[top]; // 记录下要出栈的值, top--; // 如果直接return,就不能完成top--了 return temp; } // 观察栈顶的方法 public int showTop(){ if (top==-1) { System.out.println("栈空,没有数据---"); return -1; } return stack[top]; } // 遍历栈 从栈顶开始到栈底 public void showStack(){ if (top==-1){ 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; //res 用于存放计算结果 switch (oper) { case '+': res = num1 + num2; break; case '-': res = num2 - num1; // 注意顺序 break; case '*': res = num2 * num1; break; case '/': res = num2 / num1; break; default: break; } return res; } }
测试代码如下:
public class Calculator { public static void main(String[] args) { String expression ="300+2*6-2*2"; //创建两个栈,数栈和符号栈 ArrayStack2 numStack = new ArrayStack2(10); ArrayStack2 operStack = new ArrayStack2(10); // 定义需要的相关变量 字符串不能用for直接遍历,故采用index索引扫描 int index=0; // 用于扫描 int num1=0; int num2=0; int oper=0; int res=0; char ch=' '; //将每次扫描得到的char保存到ch String keepnum=""; // 用于拼接多位数 // 开始while循环的扫描experssion while (true){ // 一次得到experssion的每一个字符 // substring方法获取返回一个索引间的字符串,charAt获取第一个 ch =expression.substring(index,index+1).charAt(0); //判断ch是什么 if (operStack.isOper(ch)){ // 如果是运算符 // 判断当前的符号栈是否为空 if (!operStack.isEmpty()){ //如果不为空 if (operStack.priority(ch)<=operStack.priority(operStack.showTop())){ num1=numStack.stackPop(); num2=numStack.stackPop(); oper=operStack.stackPop(); res=numStack.cal(num1,num2,oper); // 把运算的结果入数栈 numStack.stackPush(res); // 然后将当前的操作符入符号栈 operStack.stackPush(ch); }else { // 如果当前操作符的优先级大于栈中的操作符,直接写入 operStack.stackPush(ch); } }else { // 如果符号栈为空,则直接入符号栈 operStack.stackPush(ch); } }else { //如果是数,直接入数栈 // numStack.stackPush(ch)是错的,因为是字符串要ascll码 // numStack.stackPush(ch-48); // 分析思路 // 1、当处理多位数时,不能发现是一个数就立即出栈,可能是个多位数 // 2、在处理数,需要向expression表达式的index再看一位,如果是数就进行扫描,如果符号才入栈 // 3、因此需要定义一个变量字符串,用于拼接 // 处理多位数 keepnum+=ch; // 如果ch已经是expression最后一位,就直接入栈 if (index==expression.length()-1){ numStack.stackPush(Integer.parseInt(keepnum)); }else { // 判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入栈 // 注意只是看后一位,不是index++ if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))){ //如果后一位是运算符,则入栈keepNum=”1“或”123“ numStack.stackPush(Integer.parseInt(keepnum)); //重要的 要把keeepnum清空 keepnum=""; } } } // 让index+1,并判断是否扫描到expression最后 index++; if (index>=expression.length()){ break; } } // 当表达式扫描完毕,就顺序的从数栈和符号栈中弹出相应的数和符号,并允许 while (true){ // 如果符号栈为空,最计算得到最后结果 if (operStack.isEmpty()){ break; } num1=numStack.stackPop(); num2=numStack.stackPop(); oper=operStack.stackPop(); res=numStack.cal(num1,num2,oper); // 把运算的结果入数栈 numStack.stackPush(res); } // 将最后的数,弹出 System.out.printf("表达式%s=%d",expression,numStack.stackPop()); } }
① 中缀表达式字符串(String)转中缀表达式集合(List)
思路分析:
由于字符串再转为后缀表达式过程中不好遍历,故把其转为Lsit中缀表达式。
代码如下:
注意:
① String.charAt(int index)方法获取当前索引位置对应的字符
② 字符与数字进行比较是拿ascii码进行的
③字符与数字计算 例如 ch+=2;或ch++; 得到的ch还是字符,而system.out.println(ch+2) 输出的是ascii码。
// 将中缀表达式转为对应的中缀List public static List<String> toInfixExpression(String arr) { ArrayList<String> ls = new ArrayList<>(); // 定义一个List,存放中缀对应的内容 int index = 0;// 索引取遍历中缀表达式arr char ch; //将每次扫描得到的char保存到ch String str = ""; // 用于拼接多位数 do { // 如果ch是一个非数字,需要加入到ls中 // String.charAt(int index)方法获取当前索引位置对应的字符 // 字符与数字进行比较是拿ascii码进行的 // 字符与数字计算 例如 ch+=2;或ch++; 得到的ch还是字符 // 而system.out.println(ch+2) 输出的是ascii码 if ((ch = arr.charAt(index)) < 48 || (ch = arr.charAt(index)) > 57) { ls.add("" + ch); index++; //需要index后移 } else { //如果是一个数,需要考虑多位数 str = ""; // 先将str置成”“ while (index < arr.length() && (ch = arr.charAt(index)) >= 48 && (ch = arr.charAt(index)) <= 57) { str += ch; // 拼接 index++; } ls.add(str); } } while (index < arr.length()); return ls; //返回 }
② 中缀表达式转换后缀表达式 (将中缀List转后缀List)
具体步骤:
1、 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
2、 从左至右扫描中缀表达式;
3、 遇到操作数时,将其压入s2;
4、遇到运算符时,比较其于s1栈顶运算符的优先级:
① 如果s1为空,或栈顶运算符为左括号”(“,则直接将此运算符入栈;
② 否则,若优先级比栈顶运算符的高,也将运算符压入s
1;
③ 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(①)与此时的新栈顶运算符比较 (实际就是—弹出栈中优先级高于或等于当前运算符的所有运算符)
5、遇到界限符,遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并压入s2,直到弹出“(”为止,此时将着一对括号丢弃。
6、重复步骤2-5,直到表达式的最右边
7、将s1中剩余的运算符依次弹出并压入s2
8、依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
代码如下:
说明:因为中间结果栈s2,在整个过程中,没有pop过程,而且后面还需要逆序输出,比较麻烦。因此采用List s2。
// 方法:将得到的中缀表达式List转为后缀表达式List public static List<String> parseSuffixExpressionList(List<String> ls){ // 定义两者栈 Stack<String> s1 = new Stack<>(); // 符号栈 // Stack<String> s2 = new Stack<>(); // 中间值栈 // 说明 因为s2这个栈,在整个过程中,没有pop过程,而且后面还需要逆序输出,比较麻烦。 // 这里就不用Stack<String> s2 直接用List<String> s2 ArrayList<String> s2 = new ArrayList<>(); // 遍历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 }
优先级的比较方法如下:
// 编写一个类Operation可以返回一个运算符,对应的优先级 class Operation{ private static int ADD=1; private static int SUB=1; private static int MUL=1; private static int DIV=1; // 写一个方法,返回对应的优先级数字 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: break; } return result; } }
测试如下:
String expression = "1+((2+3)*4)-5";
List<String> infixExpressionList = toInfixExpression(expression);
System.out.println("中缀表达式对应的List"+infixExpressionList); // [1,+,(,(,2,+,3,),*,4,),-,5]
List<String> parseSuffixExpressionList = parseSuffixExpressionList(infixExpressionList);
System.out.println("后缀表达式对应的List"+parseSuffixExpressionList); // [1,2,3,+,4,*,+,5,-]
③ 后缀表达式的计算
代码如下:
// 完成逆波兰的计算 public static int calculate(List<String> ls){ //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()); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。