目录
引言:敲门砖
(👈图片from豆包) 排队在日常生活中难以避免,排队不插队成了当今社会的公序良俗,干饭人干饭魂,谁第一个冲到食堂,站在打饭窗口,谁就第一个能拿到饭后继的人排在其后,排成一列(先到先吃)。那在计算机中的队列呢?
认识Queue基础
from JDK 17 Documentation
Queue定义
队列(Queue)也是一个进行了进一步的封装和限制的线性表,提供了入队列、出队列、取队首元素。在 Java 中,Queue
是一个接口,它继承自 Collection
接口,通常遵循 FIFO(先进先出) 原则。
队列这样的数据结构,常用于辅助“耗时较长的”的操作、无法一次处理完的操作,而这就需要按照顺序,一个一个的进行处理,这时就可以将待处理的数据放到队列中,确保处理顺序不乱,先进的先处理。
Queue 使用
由于Queue 是一个接口,不能创建实例只能通过创建其他类去实现该接口,并实现抽象方法。
Queue <Integer> queue = new LinkedList<>();
// 之所以 new LinkedList 是因为其实现了Queue 接口
Queue <Integer> queue = new ArrayDeque<>();
// 基于数组 更高效、元素个数上限可控 内存使用率更高
如上图 基于数组,初始时,head 和 tail 都指向 0 位置,此时认为是一个空队列。
随着元素入队[head,tail) 前闭后开,tail
后移;元素出队,head
后移 ,head++ 后其之前指向的元素就被视为无效了,逻辑删除了。
数组是有固定容量的,当tail 走到数组末尾是否意味着队列就满了呢?
给上图变形一下
首尾相接,当head 和 tail 重合时,队列就满了 。
但上面说过 初始时 head , tail 重合,队列为空
❓那怎么区分 满 or 空
方案一:
牺牲一个格子,当 tail 走到 head 前面一个位置时就视作队列为满;
方案二:引入变量 size 当 size == 数组长度时即为满;size == 0 空
入队 size++出队 size--
这就是循环队列 ⬆️
在用代码实现循环队列前先认识一下队列的基本方法:
Queue 基本方法
方法类别 | 方法名 | 功能描述 | 队列空时 | 队列满时 |
---|---|---|---|---|
入队列 | add(E e) | 把元素添加到队列尾部 | 抛出异常 | 抛出异常 |
offer(E e) | 尝试将元素添加到队列尾部 | 抛出异常 | 返回 false | |
出队列 | remove() | 移除队首元素 | 抛出异常 | 抛出异常 |
poll() | 移除队首元素 | 返回 null | 返回 null | |
查看队首元素 | element() | 查看队首,不取出 | 抛出异常 | 抛出异常 |
peek() | 查看队首,不取出 | 返回 null | 返回 null |
黄色的方法更常用
还有 size() 获取队中元素个数
isEmpty() 判空
模拟实现队列
一:by 数组
package queue;
import java.util.Scanner;
public class MyQueueByArray {
private String[] arr = null;
private int head = 0;
private int tail = 0;
private int size = 0;
public MyQueueByArray(){
arr = new String[1000];
}
public MyQueueByArray(int capacity){
arr = new String[capacity];
}
public void offer(String val){
// 如果队列满了
if(size == arr.length){
// 可抛异常,可扩容(不建议)
return;
}
// 把新的元素放到 tail 的位置上
arr[tail] = val;
// 更新 tail 的指向
tail++;
if(tail == arr.length){
tail = 0;
}
// 更新 tail 的指向 的另一种写法
// tail = (tail + 1) % arr.length;
// 更新元素个数
size++;
}
public String poll(){
if(size == 0){
return null;
}
// 取出队首元素,保存起来,以便返回
String val = arr[head];
head++;
if(head == arr.length){
head = 0;
}
size--;
return val;
}
public String peek(){
if(size == 0){
return null;
}
return arr[head];
}
public boolean isEmpty(){
return size == 0;
}
public int size(){
return size;
}
}
二:by 单链表
package queue;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Scanner;
// 基于单链表实现队列
public class MyQueue {
// 链表的一个节点
static class Node{
public String val;
public Node next;
public Node(String val){
this.val = val;
this.next = null;
}
}
// 把队列的头部和尾部都记录下来
// 基于链表实现队列:
// 这个做法 链表的头/尾与队列的头/尾是一致的
// 1. 入队 -> 尾插
// 2. 出队 -> 头删
// 也可以
// 1. 入队 -> 头插
// 2. 出队 -> 尾删
//
private Node head = null;
private Node tail = null;
// 入队
public void offer(String val){
// 链表尾插
Node newNode = new Node(val);
// 链表为空
if(head == null){
head = newNode;
tail = newNode;
return;
}
// 非空
tail.next = newNode;
tail = newNode;
}
// 出队列
public String poll(){
// 头删
if(head == null){
return null;
}
// 保存头部节点的值
// 接下来把这个节点删除掉,返回这个值
String val = head.val;
// 更新 head 的指向
head = head.next;
// 如果链表的节点数超出 1 个 ,删掉一个元素,不影响 tail 的指向
// 但是,链表的节点数只有一个,删掉这个元素,tail 就应该指向 null
if(head == null){
tail = null;
}
return val;
}
// 取队首元素
public String peek(){
if(head == null){
return null;
}
return head.val;
}
}
实现循环队列 :
package queue;
import java.util.Scanner;
public class MyCirecleQueue {
// 创建一个数组
private int[] arr;
private int elem = 0;
private int head = 0;
private int tail = 0;
private int size = 0;
// 构造方法
public MyCirecleQueue(int cap){
this.arr = new int[cap];
}
// 判空
public boolean isEmpty() {
return size == 0;
}
// 判满
public boolean isFull() {
return size == arr.length;
}
public int size() {
return size;
}
// 入队
public boolean enqueue(int elem){
if(isFull()){
return false;
}
arr[tail] = elem;
tail = (tail + 1) % arr.length; // 利用取模运算实现循环
size++;
return true;
}
// 出队
public Integer dequeue() {
if (isEmpty()) return null;
int value = arr[head];
head = (head + 1) % arr.length;
size--;
return value;
}
// 查看队首元素
public Integer peek() {
if (isEmpty()) return null;
return arr[head];
}
}
双端队列

Deque 就不用遵守先进先出的规则了 ,更加灵活。同样其也是一个接口不能创建实例。
创建
Deque<Integer> deque = new ArrayDeque<>();
Deque<Integer> deque = new LinkedList<>();
方法
addFirst() // 从队首入队列
addLast() // 从队尾入队列
removeFirst() // 从队首出队列
removeLast() // 从队尾出队列
addFirst() + removeFirst() 相当于栈
addLast() + removeLast() 相当于队列
队列练习
1.用队列实现栈
leetcode 225 用队列实现栈
思路:1.准备A,B 两个队列
2.入栈:往 A 中 入队列
3.出栈:先把A中的所有元素循环出队列,并入队列到 B 中,当A中仅剩最后一个元素时,这个元素就是栈顶元素,删除掉。
完成一次出栈,元素都到 B 中了,方便后续入栈,交换A,B 指向
4.取栈顶:把A中元素往B中放,仅剩最后一个元素时返回其值。
A 为主 B 为辅 辅助删除元素
class MyStack {
private Queue<Integer> A = new LinkedList<>();
private Queue<Integer> B = new LinkedList<>();
public MyStack() {
//上面初始化过了,空着即可
}
// 交换 A B 指向
public void swap(){
Queue<Integer> temp = new LinkedList<>();
temp = A;
A = B;
B = temp;
}
public void push(int x) {
A.offer(x);
}
public int pop() {
while(A.size() > 1){
B.offer(A.poll());
}
int result = A.poll();
swap();
return result;
}
public int top() {
while(A.size() > 1){
B.offer(A.poll());
}
int result = A.poll();
B.offer(result);
swap();
return result;
}
public boolean empty() {
return A.isEmpty();
}
}
2.用栈实现队列
思路:准备两个栈
A专门负责入队列 B专门负责出队列
入队列,把所有B中的元素出栈 入栈到A
出队列,把所有A中的元素出站,入站到B,从B中出战
取栈顶元素,把A的元素全部转移到B中,取B的栈顶。
判定为空,确保两个站都不空,此时整体就是非空
class MyQueue {
private Stack<Integer> A = new Stack<>(); // 用于入队操作的栈A
private Stack<Integer> B = new Stack<>(); // 用于出队操作的栈B
public MyQueue() {
}
public void push(int x) {
A.push(x);
}
public int pop() {
if (B.empty()) {
while (!A.empty()) {
B.push(A.pop());
}
}
return B.pop();
}
public int peek() {
if (B.empty()) {
while (!A.empty()) {
B.push(A.pop());
}
}
return B.peek();
}
public boolean empty() {
return A.empty() && B.empty();
}
}
头脑🌪️ 上述两问是否能用双端队列解决呢 ?
人生偶尔会走上一条陌路 像是没有指标的地图 别让她们说你该知足 只有你知道什么是你的幸福
———— 二十二 DT
👍⭐️🦀🦀