栈和队列是操作受限的线性表,本质上栈和队列没啥不同,可以相互转换~~~
经典问题:用栈模拟队列,用队列模拟栈
回顾之前的线性表,无论是动态数组还是链表,都可以灵活的在任意位置进行元素的插入和删除。也就是说,无论要在当前内部任意位置进行元素的CURD操作都是不受限制的。
1.概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。 出栈:栈的删除操作叫做出栈,出数据在栈顶。
将元素添加到栈中称为压栈或入栈---push
将元素从栈中移除称为出栈---pop
无论入栈还是出栈,都只能固定在栈顶进行。
查看栈顶元素---peek():只取值,不删除
2. 栈的使用
JDK中关于栈的使用
是具体子类Stack 入栈push 出栈pop 查看栈顶元素peek
牛客上一定要记住导包操作 import java.util.*
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(3);
stack.push(5);
stack.push(7);
// 7
System.out.println(stack.pop());
// [1,3,5]
System.out.println(stack);
// 5
System.out.println(stack.peek());
}
3.栈的模拟实现
package seqlist.stack_queue.stack; import java.util.Arrays; import java.util.NoSuchElementException; /** * User:Administrator * Date:2022-11-22 * Time:15:38 * Description: 基于数组的顺序栈实现 */ public class Stack { // 当前栈中的元素个数,对应数组的下一个插入位置索引 private int size; private int[] data; public Stack(){ this(10); } public Stack(int capacity){ this.data = new int[capacity]; } // 入栈操作 - push public void push(int val){ data[size] = val; size ++; // 判断是否需要扩容 if (size == data.length){ // 简单实现,直接扩容为原来的一倍 this.data = Arrays.copyOf(data,data.length << 1); } } // 出栈 public int pop(){ if (size == 0){ // 栈中一个元素都没有 throw new NoSuchElementException("stack is empty! cannot pop!"); } // 返回当时数组中最后一个有效元素 int val = data[size - 1]; // 将最后一个有效元素进行逻辑删除,访问不到 size --; return val; } // 查看栈顶元素 public int peek(){ if (size == 0){ throw new NoSuchElementException("stack is empty! cannot peek!"); } return data[size - 1]; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("boottom ["); for (int i = 0; i < size; i++) { sb.append(data[i]); if (i < size - 1) { sb.append(","); } } sb.append("] top"); return sb.toString(); } }
public static void main(String[] args) { Stack stack = new Stack(); stack.push(1); stack.push(3); stack.push(5); stack.push(7); // 7 System.out.println(stack.pop()); // [1,3,5] System.out.println(stack); // 5 System.out.println(stack.peek()); // [1,3,5] System.out.println(stack); }
4.栈的应用场景
4.1 改变元素的序列
1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是(C)
A: 1,4,3,2 B: 2,3,4,1 C: 3,1,4,2 D: 3,4,2,1
2.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( B)。
A: 12345ABCDE B: EDCBA54321 C: ABCDE12345 D: 54321EDCBA
解析:典型的LIFO!
4.2 可以使用栈来倒序输出链表的节点值
依次将链表节点值保存到栈中
依次pop即可倒序输出链表的节点值
public void printReverse() {
printReverse(head);
}
// 使用栈来实现链表节点的倒序输出
public void printReverseByStack() {
Stack<Integer> stack = new Stack<>();
for (Node x = head;x != null;x = x.next) {
stack.push(x.val);
}
// 依次出栈打印输出即可!
while (!stack.isEmpty()) {
int val = stack.pop();
System.out.print(val + " ");
}
}
5.概念区分
栈、虚拟机栈、栈帧有什么区别呢?
1.此处的栈是数据结构的栈,线性表的一种,满足后进先出的特点(LIFO)。
保存在栈中的元素都是相同的数据类型。
2.虚拟机栈,操作系统栈:都是概念性的东西,至于底层是不是用的栈(数据结构),到底用的是链式栈还是顺序栈都不是关键。
3.栈帧:函数从调用过程到结束的一个体现,一个函数从调用到销毁其中占用的空间,内部的局部变量都放在该函数的栈帧中(一种类型,专门描述函数在操作系统内部的调用过程)。
可以相当于class定义一个类