
一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出(先进后出) LIFO(Last In First Out)的原则。 压栈(进栈):栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。 出栈:栈的删除操作叫做出栈。出数据在栈顶。

栈在我们日常生活中的例子:


import java.util.Stack;
public class MyStack {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);//以上为压栈操作
Integer x = stack.peek();
System.out.println(x);//获取栈顶元素
System.out.println(stack.size());//获取栈内有效元素个数
System.out.println(stack.isEmpty());//检查栈是否为空
Integer y = stack.pop();
System.out.println(y);//出栈操作
int z = stack.size();
System.out.println(z);
}
}
注意:

栈是一个特殊的顺序表,所以采用链表和数组的方式都可实现,但是,一般采用数组的方式实现.
usedSize来表示栈中有效元素个数,入栈一个元素则usedSize++,出栈一个元素则usedSize--,所以此时usedSize有两个含义: ①可以表示当前数据存放的个数 ②表示当前存放数据的下标 说明:usedSize初始值为0,我们往栈里添加元素,将元素添加到下标为0的数组中,此时usedSize为0表示当前存放数据的下标,添加1个元素后,进行usedSize++操作,此时usedSize表示当前数据存放的个数 push()入栈:
pop()出栈:
usedSize 的值,我们实际上是在告诉栈:“我们刚刚移除了一个元素,所以现在的栈比刚才小了一个元素
为什么返回的是useSize-1的值而不是usedSize的值?
因为在我们进行入栈操作时是先使用的usedSize来表示下标来进行添加元素操作的,添加一个元素usedSize就++,所以此时我们想返回栈顶元素时,栈顶元素的下标就是usedSize-1
如何进行空间释放?
因为我们栈中的数据时简单类型,只需要将表示数组中元素个数的usedSize--即可,当下一次进行入栈操作时直接将元素覆盖即可,但如果是引用类型的数据则需要进行置空操作import java.util.Arrays;
public class MyStack {
public int[] elem;
public int usedSize;
public MyStack() {
this.elem = new int[10];
}
public void push(int val){
if (isFull()) {
//如果满则进行扩容,此处进行两倍扩容
elem = Arrays.copyOf(elem,elem.length * 2);
}
elem[usedSize] = val;//将元素按顺序进行压栈
usedSize++;
}
public boolean isFull() {
//判断栈是否满
return usedSize == elem.length;
}
public int pop() {
//将栈顶元素出栈,要求弹出栈顶上一个元素的值
//如果usedSize为空则无法抛出
if (empty()) {
throw new RuntimeException("栈中没有元素,不能出栈");
}
int oldV = elem[usedSize - 1];//存储上一个元素的值
usedSize--;
return oldV;
}
public boolean empty() {
return usedSize == 0;
}
public int peek() {
if (empty()) {
return 0;
}
return elem[usedSize - 1];
}
}刚刚实现的栈或者说Java集合类的Stack底层是一个数组。这种栈叫做顺序栈(基于数组)!
那栈是否可以使用链表来实现呢,链表实现的栈叫链式栈(基于链表)
如果是单链表那么入栈和出栈最好从链表头部进行入栈出栈。O(1)相反如果从链表的头部或者尾部进行入栈和出栈O(N)
如果是双向链表来实现栈那么复杂度均是O(1)
public static void main(String[] args) {
LinkedList<Integer> stack = new LinkedList<>();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(stack.peek());
stack.pop();
System.out.println(stack.size());
}链栈与顺序栈相比,比较明显的优点是入栈时不需要扩容;
链式结构实现栈时,每次入栈相当于链表中头插一个节点,没有扩容一说
如果是链栈,一般需要进行头插或者头删操作,而顺序栈一般进行尾插和尾删操作,链表的操作比顺序表复杂,因此使用顺序结构实现栈更简单

https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/
解题之前我们先理解什么叫中缀表达式和后缀表达式 中缀表达式: 该表达式就是我们平常学习中常用的表达式,例如:
(3 + 4) * 5它的特点为:
后缀表达式(逆波兰表达式):
例如:3 4 + 5 *(相当于 (3 + 4) * 5,结果是 35)
如何将中缀表达式转为后缀表达式呢? 这里我们通过两个例子来进行演示:

对应转换为种植表达式就是:1 +2*3 +(4*5 +6)*7 = 189
总结:
后缀表达式是如何存储到栈中并计算呢? 是操作数就放入栈中,直到遇到运算符,弹出栈顶的第一个元素作为右操作数,第二个作为左操作数,计算出的结果再放入栈中,重复这一操作即可

class Solution {
public int evalRPN(String[] tokens) {
//思路:将数组中的操作数进行压栈操作,当遇到运算符时,则将栈顶元素拿出来作为右操作数,再取一次作为左操作数
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < tokens.length; i++) {
//操作数和运算符要进行不同的操作
String tmp = tokens[i];
if (isoperation(tmp)) {
//为运算符时,则赋值左右操作数
Integer val2 = stack.pop();
Integer val1 = stack.pop();
//拿出来个操作数后,可能会进行+-*/四个操作
switch (tmp) {
case "+" -> stack.push(val1 + val2);
case "-" -> stack.push(val1 - val2);
case "*" -> stack.push(val1 * val2);
case "/" -> stack.push(val1 / val2);
}
}else {
//为操作数时,,则进行入栈操作
Integer val = Integer.valueOf(tmp);
stack.push(val);
}
}
return stack.pop();
}
public boolean isoperation(String s) {
if (s.equals("+") || s.equals("-") ||s.equals("*") || s.equals("/")) {
return true;
}else {
return false;
}
}
}https://leetcode.cn/problems/valid-parentheses/description/
分四种情况:
只有当获取的字符串为左括号时我们才进行入栈操作,当遇到右括号时我们分为两种情况:①当第一个元素就是右括号,此时栈为空,直接返回false即可②如果右括号不是第一个元素,我们就看当前元素是否与栈顶的元素匹配,若匹配则将栈顶的元素出栈,若不匹配则返回false 当整个循环走完,栈为空时则为true 例:()))),栈内不为空则为false 例:()((
class Solution {
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 == '{') {
//1.为左括号直接入栈
stack.push(ch);
}else {
//2.为右括号时分两种情况:s中第一个元素就是右括号,此时栈为空直接返回false即可
// 栈内有左括号,依次进行匹配()[]
if (stack.empty()) {
return false;
}else {
char chL = stack.peek();
//将栈顶元素与给定的字符串进行匹配,如果匹配那么将栈内与之匹配的左括号进行出栈操作,最终栈内为空则为true
if (chL == '(' && ch == ')' || chL == '[' && ch == ']' || chL == '{' && ch == '}') {
stack.pop();
//这里不能直接返回true,因为栈中可能还有元素
}else {
//括号与之不匹配(]{]则返回false
return false;
}
}
}
}
return stack.empty();
}
}pushV数组表示压栈后的数组,popV数组表示出栈后的数组可能弹出的顺序
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pushV int整型一维数组
* @param popV int整型一维数组
* @return bool布尔型
*/
public boolean IsPopOrder (int[] pushV, int[] popV) {
// write code here
Stack<Integer> stack = new Stack<>();
int j = 0;
//1.遍历pushv数组
for(int i = 0; i < pushV.length; i++) {
stack.push(pushV[i]);
while(j < popV.length && !stack.empty() && stack.peek() == popV[j]){
stack.pop();
j++;
}
}
return stack.empty();
}
}https://leetcode.cn/problems/min-stack/
这里我们的设计思路是使用两个栈,第一个普通栈是为了满足top()方法返回栈顶元素;第二个栈为最小栈是为了满足getMin()方法返回栈的最小元素,此时我们限制最小栈入栈的规则,使得栈顶为最小的元素,直接返回最小栈的栈顶元素即可 存放元素push的过程:
取元素的过程:pop()
top ==> peek()返回值是普通栈的值 getMin()获取最小栈的栈顶元素
class MinStack {
Stack<Integer> stack;
Stack<Integer> minStack;
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
public void push(int val) {
//不论是第几次入栈,stack都要入栈
stack.push(val);
//最小栈第一次入栈时需要放元素,之后的入栈都需要将val与最小栈的栈顶元素进行比较
if(minStack.empty()) {
minStack.push(val);
}else {
if(val <= minStack.peek()){
minStack.push(val);
}
}
}
public void pop() {
if(stack.empty()) {
return;
}
int popval = stack.pop();
if(minStack.peek() == popval) {
minStack.pop();
}
}
public int top() {
if(stack.empty()){
return -1;
}
return stack.peek();
}
public int getMin() {
if(minStack.empty()){
return -1;
}
return minStack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(val);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/