栈是一个先入后出的有序列表,是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶定(top),另一端为固定的一端,称为栈底(bottom)。根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
入栈图解:
出栈图解:
应用场景:
1)子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完毕之后再将地址取出,以回到原来的程序中。
2)处理递归调用:和子程序的调用类似,只是除了储存下一指令的地址外,也将参数、区域变量等数据存入堆栈中。
3)表达式的转换[中缀表达式-->后缀表达式]与求值(实际解决)。
4)二叉树的遍历。
5)图形的深度优先搜索法。
案例:
1.用数组模拟栈的使用
思路分析:
定义一个变量指针top,初始化top = -1,始终指向栈顶元素。
入栈操作push:当有数据加入到栈时,top++;stack[top];
出栈操作pop:从栈顶取出数据并返回,int value = stack[top];top--;return value;
public class Stack { public int maxSize; public int top = -1; public int[] stack; // 成员变量会初始化 // 构造器(记牢这个写法) public Stack(int maxSize) { this.maxSize = maxSize; stack = new int[maxSize]; } // 判栈满 public boolean isFull(){ /*if(top == maxSize - 1){ return true; }else return false;*/ return top == maxSize; } // 判栈空 public boolean isEmpty(){ return top == -1; } // 加元素于栈顶 public void push(int value){ // 判栈满 if(isFull()){ System.out.println("栈满,无法push!"); return; // 直接结束方法 } stack[++top] = value; } // 从栈顶弹出元素 public int pop(){ // 判栈空 if(isEmpty()){ // System.out.println("栈空,无法pop!"); // 抛出异常处理 // throw关键字用在方法内,用来抛出一个异常对象 // 将这个异常对象传递到调用者处,并结束当前方法的执行。 // 无需再声明异常,默认交给JVM处理(打印异常对象,中断程序) throw new RuntimeException("栈空,无法pop!"); // 同样跳出方法 } return stack[top--]; } // 遍历栈,从栈顶往下遍历 public void show(){ if(isEmpty()){ System.out.println("栈空,无法遍历"); return; // 结束方法 } for(int i = top;i > -1;i--){ System.out.println(stack[i]); } } }
2.用单链表模拟栈
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } */ public class LinkedListStack { public int maxSize; // 容量 public int top = -1; // 栈顶指针 public ListNode listNode; // 链表 // 初始化 public LinkedListStack(int maxSize) { this.maxSize = maxSize; } // 判空 public boolean isEmpty(){ return listNode == null; } // 判满,链表元素个数和maxSize相同 public boolean isFull(){ int count = 0; ListNode node = listNode; // 遍历指针 while(node != null){ count ++; node = node.next; } return count == maxSize; } // 往栈顶添加元素 public void push(int value){ // 判满 if(isFull()){ System.out.println("栈满,无法插入元素!"); return; } // 头插法 if(isEmpty()) listNode = new ListNode(value); // 第一个元素 else { // 其他元素 ListNode node = new ListNode(value); // 默认指针域null node.next = listNode; listNode = node; } } // 弹出栈顶元素 public int pop(){ // 判空 if(isEmpty()){ throw new NullPointerException("栈空,无法弹出元素"); // 异常,直接终止 } int temp = listNode.val; listNode = listNode.next; return temp; } // 遍历(与输入顺序相反,刚好满足头插,所以直接顺序遍历即可) public void show(){ // 判空 if(isEmpty()){ System.out.println("栈空,无法遍历"); return; // 退出方法 } ListNode node = listNode; // 遍历指针 while(node != null){ System.out.println(node.val); node = node.next; } } }
3.使用栈来实现综合计算器--中缀问题
两个栈:一个专门存放数,一个专门存放运算符。
步骤:
1.通过index
扫遍历字符串表达式;
2.当扫描到的是数字,直接入数栈;
3.当扫描的是运算符,分情况入栈。
4.如果当前符号栈为空,该运算符直接入栈;如果当前符号栈不为空(有运算符),就对栈顶元素运算符与该运算符进行优先级比较(小于等于,就从数栈pop出两个数,从符号栈pop出一个运算符,进行运算,将得到的结果再入数栈,该运算符也直接入栈,(注意此处并非继续与当前符号栈下一个栈顶元素比较,直到优先级大于栈顶元素或栈空入栈,因为这样就增加了遍历的时间复杂度)
5.当表达式扫描完毕,就顺序地从数栈和符号栈中pop出相应的数和运算符,并运算。
6.最后在数栈只有一个数字,就是表达式的结果。
案例实现(3+2*6-2)
Stack numberStack = new Stack(10); Stack operStack = new Stack(10); String str = "3+2*6-2"; int num1 = 0; int num2 = 0; int oper = 0; char[] chars = str.toCharArray(); // 转为数组,以便遍历 for (char val:chars) { // 判断val是什么 if(operStack.isOper(val)){ // 如果是运算符 // 判断当前符号栈是否为空 if(operStack.isEmpty()){ // 直接入栈 operStack.push(val); }else{ // 不为空 char top = (char) operStack.stack[operStack.top]; // 栈顶值 // 判断栈顶元素和当前元素的优先级 if(operStack.priority(val) <= operStack.priority(top)){ // 自动转换 // 当前元素优先级优先级小于等于栈顶元素优先级,则取出来运算 num1 = numberStack.pop(); // 先 num2 = numberStack.pop(); // 后 oper = operStack.pop(); // 运算符 numberStack.push(numberStack.cal(num1,num2,oper)); // 运算 operStack.push(val); }else{ // 当前运算符优先级大于栈顶元素优先级 operStack.push(val); } } }else{ // 是数,直接入数栈 numberStack.push(val - '0'); } } // 扫描完毕后,相应pop运算到数栈只剩结果 while(operStack.isEmpty()){ // 符号栈为空,数栈只剩一个结果 num1 = numberStack.pop(); // 先 num2 = numberStack.pop(); // 后 oper = operStack.pop(); // 运算符 numberStack.push(numberStack.cal(num1,num2,oper)); // 运算 } // 打印结果 System.out.print("表达式" + str); System.out.println("运算结果为:" + numberStack.pop());