文章目录
- 1. 栈(Stack) 的概念
- 2. 栈的模拟实现
- 3. 栈的使用
- 4. 栈的应用场景
- 4.1. 改变元素的序列
- 4.2. 将递归转化为循环
- 4.3. 150.逆波兰表达式求值
- 4.4. 20.括号匹配
- 4.5 JZ31 栈的压入、弹出序列
- 4.6 155.最小的栈
- 5. 概念区分
1. 栈(Stack) 的概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守 后进先出LIFO(Last In First Out) 的原则。
【后进先出LIFO原则】:
压栈(Push()):栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈(Pop()):栈的删除操作叫做出栈。出数据在栈顶。
栈在现实生活中的例子:
2. 栈的模拟实现
从上图中可以看到,Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的。
public class MyStack {int[] array;int size;public MyStack(){array = new int[3];}public int push(int e){ensureCapacity();array[size++] = e;return e;}public int pop(){int e = peek();size--;return e;}public int peek(){if(empty()){throw new RuntimeException("栈为空,无法获取栈顶元素");}return array[size-1];}public int size(){return size;}public boolean empty(){return 0 == size;}private void ensureCapacity(){if(size == array.length){array = Arrays.copyOf(array, size*2);}}
}
3. 栈的使用
方法 | 功能 |
---|---|
Stack() | 构造一个空的栈 |
E push(E e) | 将e入栈,并返回e |
E pop() | 将栈顶元素出栈并返回 |
E peek() | 获取栈顶元素 |
int size() | 获取栈中有效元素个数 |
boolean empty() | 检测栈是否为空 |
public static void main(String[] args) {Stack<Integer> s = new Stack();s.push(1);s.push(2);s.push(3);s.push(4);System.out.println(s.size()); // 获取栈中有效元素个数---> 4System.out.println(s.peek()); // 获取栈顶元素---> 4s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3if(s.empty()){System.out.println("栈空");}else{System.out.println(s.size());}
}
4. 栈的应用场景
4.1. 改变元素的序列
若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
A: 1,4,3,2 B: 2,3,4,1 C: 3,1,4,2 D: 3,4,2,1
答案:为C,C选项中先出的元素为3,说明之前入栈的元素为1、2、3,3出栈之后,出栈顺序一定是2在1之前,2不出来,1不可能先出来。一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。
A: 12345ABCDE B: EDCBA54321 C: ABCDE12345 D: 54321EDCBA
答案为B
4.2. 将递归转化为循环
比如:逆序打印链表
// 递归方式
void printList(Node head){if(null != head){printList(head.next);System.out.print(head.val + " ");}
}// 循环方式
void printList(Node head){if(null == head){return;}Stack<Node> s = new Stack<>();// 将链表中的结点保存在栈中Node cur = head;while(null != cur){s.push(cur);cur = cur.next;}// 将栈中的元素出栈while(!s.empty()){System.out.print(s.pop().val + " ");}
}
4.3. 150.逆波兰表达式求值
150.逆波兰表达式求值题目链接
逆波兰表达式介绍
逆波兰表达式又叫做后缀表达式。
- 表达式一般由操作数(Operand)、运算符(Operator)组成。
- 算术表达式中,通常把运算符放在两个操作数的中间,这称为中缀表达式(InfixExpression),如A+B。
- 把运算符写在操作数之前,称为波兰表达式(Polish Expression)或前缀表达式(Prefix Expression),如+AB;
- 把运算符写在操作数之后,称为逆波兰表达式(Reverse Polish Expression)或后缀表达式(Suffix Expression),如AB+;
假设有一个中缀表达式a+bc-(d+e):
1.首先将这个中缀表达式的所有运算加括号((a+(bc))-(d+e))
2.然后将所有运算符放到括号后面,这样就变成了((a(bc) )+ (de)+ )-
5. 把所有括号去掉abc*+de+ -,最后得出的结果就是后缀表达式。*
【题目描述】:
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
- 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
【解题】:
1、循环扫描语法单元的项目。
2、如果扫描的项目是操作数,则将其压入操作数堆栈,并扫描下一个项目。
3、如果扫描的项目是一个二元运算符,则对栈的顶上两个操作数执行该运算。
4、将运算结果重新压入堆栈。
5、重复步骤2-4,堆栈中即为结果值。
public int evalRPN(String[] tokens) {Stack<Integer> stack = new Stack<>();for(int i=0;i<tokens.length;i++){String str = tokens[i];if(!isOperation(str)) {int num = Integer.parseInt(str);stack.push(num);}else {int num1 = stack.pop();int num2 = stack.pop();switch(str){case "+":stack.push(num2+num1);break;case "-":stack.push(num2-num1);break;case "*":stack.push(num2*num1);break;case "/":stack.push(num2/num1);break;}}}return stack.pop();}public boolean isOperation(String str) {if (str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/")) {return true;}return false;}
4.4. 20.括号匹配
【题目描述】:
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号
【解题】:
只有三种情况下,括号不匹配,只要解决这三种情况,那么剩下的都是括号匹配的情况。
解题步骤:
1.如果碰到左括号那么就把它放入栈中;
2.到遇到右括号时,应该和最后一个左括号(在栈中出栈的第一个数据)进行匹配,如果匹配就出栈。
3.最后判断栈中和输入的字符串中的元素是否为空
public boolean isValid(String s) {Stack<Character> stack = new Stack<>();for (int i = 0; i < s.length(); i++) {// 获取字符串中的元素char ch = s.charAt(i);// 判断是否是左括号,如果是左括号,那么就放进栈中if (ch == '(' || ch == '[' || ch == '{') {stack.push(ch);// 如果是右括号就与栈中的左括号进行匹配} else {if (stack.empty()) {// 此时,栈为空,但是字符串不为空,右括号多,不符合括号匹配的情况return false;}// 获取最后一个左括号char tmp = stack.peek();// 进行括号匹配if (tmp == '(' && ch == ')' || tmp == '[' && ch == ']' || tmp == '{' && ch == '}') {stack.pop();} else {return false;}}}// 此时,如果栈不为空说明左括号多,栈为空说明左括号和右括号匹配return stack.empty();}
4.5 JZ31 栈的压入、弹出序列
JZ31 栈的压入、弹出序列
【题目描述】:
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
【解题】:
1.遍历push数组,把元素放到栈中
2.每push一个元素,就和pop数组中的元素比较
3.如果相等j++并且出栈
4.如果不相等,就继续入栈
public boolean IsPopOrder (int[] pushV, int[] popV) {Stack<Integer> stack = new Stack<>();int j = 0;for (int i = 0; i < pushV.length; i++) {stack.push(pushV[i]);//如果栈中的栈顶的元素和弹出序列数组的元素相同,那么元素出栈并且j++while (!stack.empty() && j < popV.length && stack.peek() == popV[j]) {stack.pop();j++;}}//此时栈的压入顺序数组执行完了,如果循环执行结束,栈中还有元素说明弹出序列和压入序列不对应return stack.empty();}
4.6 155.最小的栈
155.最小的栈
【题目描述】:
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。实现 MinStack 类:
- MinStack() 初始化堆栈对象。
- void push(int val) 将元素val推入堆栈。
- void pop() 删除堆栈顶部的元素。
- int top() 获取堆栈顶部的元素。
- int getMin() 获取堆栈中的最小元素。
【解题】:
方法:辅助栈
按照如图的思路,我们需要设计一个数据结构,使得每个元素与其相应的最小值保持对应。因此我们可以使用一个辅助栈,用于存储元素对应的最小值。
- 当一个元素要入栈时,普通的栈一定要放元素,对于辅助栈如果是空的时,我们要放元素,不为空时我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值或者相等值,如果该元素较小或者相等,就将该元素插入辅助栈中;
- 当一个元素要出栈时,如果与当前辅助栈的栈顶存储的最小值相等,那我们把辅助栈的栈顶元素也一并弹出;不相等时只需要弹出普通栈的值。
- 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。
class MinStack {Stack<Integer> stack;Stack<Integer> minStack;public MinStack() {stack = new Stack<>();minStack = new Stack<>();}public void push(int val) {//普通栈一定要放元素元素stack.push(val);//如果辅助栈为空时,也要放元素if (minStack.empty()) {minStack.push(val);} else {//不为空时,如果该元素小于或者等于辅助栈的栈顶存储的最小值,就将该元素插入辅助栈中int peek = minStack.peek();if (val <= peek) {minStack.push(val);}}}public void pop() {//首先弹出普通栈的值int pop = stack.pop();//如果普通栈的值与当前辅助栈的栈顶存储的最小值相等,那我们把辅助栈的栈顶元素也一并弹出if (!minStack.empty()) {if (pop == minStack.peek()) {minStack.pop();}}}public int top() {if (!stack.empty()) {return stack.peek();}return -1;}public int getMin() {if (!minStack.empty()) {return minStack.peek();}return -1;}
}
5. 概念区分
栈、虚拟机栈、栈帧有什么区别呢?
栈:栈是一种数据结构,它是一种后进先出(LIFO)的数据结构,只能在栈顶进行插入和删除操作。栈可以用于实现函数调用、表达式求值、内存管理等功能。
虚拟机栈:虚拟机栈是指在计算机中运行的程序中,每个线程都有自己的虚拟机栈,用于存储线程中方法的局部变量、操作数栈、动态链接、返回地址等信息。虚拟机栈的大小可以在程序运行时动态调整。
栈帧:栈帧是指在程序执行过程中,每个方法在虚拟机栈中所占用的一块内存空间,用于存储方法的局部变量、操作数栈、动态链接、返回地址等信息。当一个方法被调用时,会在虚拟机栈中创建一个新的栈帧,当方法执行结束时,栈帧会被销毁。
因此,栈是一种数据结构,虚拟机栈是指程序运行时的内存空间,而栈帧是指虚拟机栈中存储方法信息的一块内存空间。