![](https://i-blog.csdnimg.cn/blog_migrate/f8873746a9cda250e941c7678337d0d0.png)
本文将栈与队列的相关知识点基本都覆盖了,更有各种图解及应用帮助大家理解什么是栈和队列,如何去使用栈和队列,并有多个题型来帮助大家巩固所学,文末更是有相关的面试题,并附上了十分详细的图解题解,希望对大家有所帮助!!!
目录
1. 栈(Stack)
1.1 概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
栈在生活中的例子:
1.2 栈的使用
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.3 栈的模拟实现
从上图中可以看到,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);
}
}
}
1.4 栈的应用场景
1. 改变元素的序列
1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
A: 1,4,3,2 B: 2,3,4,1 C: 3,1,4,2 D: 3,4,2,1A.1进栈后就出栈,然后2,3,4依次进栈然后依次出栈
B.1,2进栈,然后2出栈,3进栈然后出栈,4进栈然后出栈,最后1出栈
C.3要最先出栈,所以1,2,3要先依次进栈,3出栈后栈顶元素为2,所以1无法第二个出栈
D.1,2,3依次进栈,然后3出栈,4进栈然后出栈,接下来2出栈,1出栈
2.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。
A: 12345ABCDE B: EDCBA54321 C: ABCDE12345 D: 54321EDCBA因为是依次入栈,最先进栈的会在栈的底部,所以栈中应该是: 栈顶 EDCBA54321 栈底 ,又因为出栈时只能从栈顶元素开始出栈,所以出栈顺序为EDCBA54321
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 + " ");
}
}
3. 括号匹配
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
//1.遍历字符串
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
//2.判断是否是左括号
if (ch == '(' || ch == '[' || ch == '{') {
stack.push(ch);
} else {
//3.右括号
//3.1栈空
if (stack.isEmpty()) {
return false;
}
//3.2栈不为空
char ch2 = stack.peek();//左括号
if (ch == ')' && ch2 == '(' || ch == ']' &&
ch2 == '[' || ch == '}' && ch2 == '{') {
stack.pop();
} else {
return false;
}
}
}
if (!stack.isEmpty()) {
return false;
}
return true;
}
}
4. 逆波兰表达式求值
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for(int i = 0; i < tokens.length; i++) {
String str = tokens[i];
if(!noNum(str)) {
//是数字,说明不是字符
int val = Integer.valueOf(str);
stack.push(val);
}else{
int num2 = stack.pop();
int num1 = stack.pop();
//字符
switch(str) {
case "+":
stack.push(num1 + num2);
break;
case "-":
stack.push(num1 - num2);
break;
case "*":
stack.push(num1 * num2);
break;
case "/":
stack.push(num1 / num2);
break;
}
}
}
return stack.pop();
}
private static boolean noNum(String str) {
if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/")){
return true;
}
return false;
}
}
5. 出栈入栈次序匹配
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pushV int整型一维数组
* @param popV int整型一维数组
* @return bool布尔型
*/
public boolean IsPopOrder (int[] pushV, int[] popV) {
// write code here
int n = popV.length;
//辅助栈
Stack<Integer> s = new Stack<>();
//入栈的下标
int j = 0;
//遍历出栈的数组
for(int i = 0; i < popV.length; i++) {
//遍历入栈的数组
//1.当栈为空且栈顶元素不等于当前出栈数组元素时
while(j < n && (s.isEmpty() || s.peek() != popV[i])) {
s.push(pushV[j]);
j++;
}
//2.栈顶元素等于出栈数组元素时
if(s.peek() == popV[i]) {
s.pop();
} else{
return false;
}
}
return true;
}
}
6.最小栈
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 peekNum = minStack.peek();
if(peekNum >= val) {
minStack.push(val);
}
}
}
public void pop() {
int popNum = stack.pop();
if(popNum == minStack.peek()){
minStack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
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();
*/
1.5 概念区分
栈、虚拟机栈、栈帧有什么区别呢?
简单来说,栈是一种数据结构,虚拟机栈是JVM(java virtual machine)的内存,栈帧是调用方法时开辟的内存。
2. 队列(Queue)
2.1 概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头(Head/Front)
2.2 队列的使用
在Java中,Queue是个接口,底层是通过链表实现的。
注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。
2.3 队列模拟实现
队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有两种:顺序结构 和 链式结构。那么队列的实现使用顺序结构还是链式结构好?
public class Queue {
// 双向链表节点
public static class ListNode {
ListNode next;
ListNode prev;
int value;
ListNode(int value) {
this.value = value;
}
}
ListNode first; // 队头
ListNode last; // 队尾
int size = 0;
// 入队列---向双向链表位置插入新节点
public void offer(int e) {
ListNode newNode = new ListNode(e);
if (first == null) {
first = newNode;
// last = newNode;
} else {
last.next = newNode;
newNode.prev = last;
// last = newNode;
}
last = newNode;
size++;
} // 出队列---将双向链表第一个节点删除掉
public int poll() {
// 1. 队列为空
// 2. 队列中只有一个元素----链表中只有一个节点---直接删除
// 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除
int value = 0;
if (first == null) {
return null;
} else if (first == last) {
last = null;
first = null;
} else {
value = first.value;
first = first.next;
first.prev.next = null;
first.prev = null;
}
--size;
return value;
} // 获取队头元素---获取链表中第一个节点的值域
public int peek() {
if (first == null) {
return null;
}
return first.value;
}
public int size() {
return size;
}
public boolean isEmpty() {
return first == null;
}
}
2.4 循环队列
实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列通常使用数组实现。
数组下标循环的小技巧
1. 下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length
2. 下标最前再往前(offset 小于 array.length): index = (index + array.length - offset) % array.length
如何区分空与满
1. 通过添加 size 属性记录
2. 保留一个位置
3. 使用标记
3. 双端队列 (Deque)
双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队
Deque是一个接口,使用时必须创建LinkedList的对象。
在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口。
Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现
4. 面试题
4.1 用队列实现栈
要用队列去实现一个栈,用一个队列可不可行?注意:这里的队列指的是普通队列,不是双端队列,我们可以试想一下,假设往一个队列中存12 和 23两个数据,队列“先进先出”,12先进队列,那么12就会先出队列,而栈“先进后出”,12先进栈中,23却是先出栈的,显然一个队列无法实现栈,如下图所示:
所以这里我们采用两个队列来实现栈
1.当两个队列是空时,把数据放入第一个队列中,如下图:
2. 我们要出栈便可以把12和23放到qu2,剩下的qu1的34便是我们要出的元素,如下图
3.如果我们需要往栈中放入元素时候,也就是再次“入栈”时,把数据放到不为空的队列,原因很简单,就是为了让其中一个队列为空,方便让我们入栈和出栈的操作,如下图插入元素45
4.“出栈”时,出不为空的队列,出size-1个元素,剩下的便是要出栈的元素
具体操作步骤如下:
- 当两个队列是空时,把数据放入第一个队列中
- 再次“入栈”时,数据放到不为空的队列
- “出栈”时,出不为空的队列,出size-1个元素,剩下的便是要出栈的元素
代码实现
class MyStack {
private Queue<Integer> qu1;
private Queue<Integer> qu2;
public MyStack() {
qu1 = new LinkedList<>();
qu2 = new LinkedList<>();
}
public void push(int x) {
if(empty()) {
qu1.offer(x);
return;
}
if(!qu1.isEmpty()) {
qu1.offer(x);
}else{
qu2.offer(x);
}
}
public int pop() {
if(empty()){
return -1;
}
if(!qu1.isEmpty()) {
int size = qu1.size();
for(int i = 0; i < size - 1; i++) {
qu2.offer(qu1.poll());
}
return qu1.poll();
}else{
int size = qu2.size();
for(int i = 0; i < size - 1; i++) {
qu1.offer(qu2.poll());
}
return qu2.poll();
}
}
public int top() {
if(empty()){
return -1;
}
if(!qu1.isEmpty()) {
int size = qu1.size();
int tmp = -1;
for(int i = 0; i < size; i++) {
tmp = qu1.poll();
qu2.offer(tmp);
}
return tmp;
}else{
int size = qu2.size();
int tmp = -1;
for(int i = 0; i < size; i++) {
tmp = qu2.poll();
qu1.offer(tmp);
}
return tmp;
}
}
public boolean empty() {
return qu1.isEmpty() && qu2.isEmpty();
}
}
4.2 用栈实现队列
跟上题一样,一个队列也无法实现队列,如下图:当队列要出12的时候,栈只能出34
跟上题一样,我们需要两个栈来实现
当我们要出元素时候,只需要将s1中的元素都放到s2去,然后出s2的栈顶元素即可
当我们要添元素时候,把元素放到第一个栈中
具体思路:
- "入队":把数据放到第一个栈中;
- “出队”:出s2的栈顶元素,如果s2为空,把s1中的元素都放到s2中
- 当两个栈都为空时,说明队列为空
代码实现
import java.util.Stack;
class MyQueue {
private Stack<Integer> s1 ;
private Stack<Integer> s2 ;
public MyQueue() {
s1 = new Stack<>();
s2 = new Stack<>();
}
public void push(int x) {
s1.push(x);
}
public int pop() {
if(empty()) {
return -1;
}
if(s2.isEmpty()) {
//弹出s1当中所有的元素 放到s2中
while(!s1.isEmpty()) {
s2.push(s1.pop());
}
}
return s2.pop();
}
public int peek() {
if(empty()) {
return -1;
}
if(s2.isEmpty()) {
//弹出s1当中所有的元素 放到s2中
while(!s1.isEmpty()) {
s2.push(s1.pop());
}
}
return s2.peek();
}
public boolean empty() {
return s1.isEmpty() && s2.isEmpty();
}
}
感谢观看,希望对你有所帮助! ! !