栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守 后进先出LIFO(Last In First Out) 的原则。
压栈(Push()):栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈(Pop()):栈的删除操作叫做出栈。出数据在栈顶。
从上图中可以看到,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);
}
}
}
方法 | 功能 |
---|---|
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()); // 获取栈中有效元素个数---> 4
System.out.println(s.peek()); // 获取栈顶元素---> 4
s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3
if(s.empty()){
System.out.println("栈空");
}else{
System.out.println(s.size());
}
}
若进栈序列为 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
比如:逆序打印链表
// 递归方式
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 + " ");
}
}
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;
}
【题目描述】:
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 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();
}
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();
}
155.最小的栈
【题目描述】:
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。实现 MinStack 类:
【解题】:
方法:辅助栈
按照如图的思路,我们需要设计一个数据结构,使得每个元素与其相应的最小值保持对应。因此我们可以使用一个辅助栈,用于存储元素对应的最小值。
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;
}
}
栈、虚拟机栈、栈帧有什么区别呢?
栈:栈是一种数据结构,它是一种后进先出(LIFO)的数据结构,只能在栈顶进行插入和删除操作。栈可以用于实现函数调用、表达式求值、内存管理等功能。
虚拟机栈:虚拟机栈是指在计算机中运行的程序中,每个线程都有自己的虚拟机栈,用于存储线程中方法的局部变量、操作数栈、动态链接、返回地址等信息。虚拟机栈的大小可以在程序运行时动态调整。
栈帧:栈帧是指在程序执行过程中,每个方法在虚拟机栈中所占用的一块内存空间,用于存储方法的局部变量、操作数栈、动态链接、返回地址等信息。当一个方法被调用时,会在虚拟机栈中创建一个新的栈帧,当方法执行结束时,栈帧会被销毁。
因此,栈是一种数据结构,虚拟机栈是指程序运行时的内存空间,而栈帧是指虚拟机栈中存储方法信息的一块内存空间。