【Java/数据结构】队列(Quque)

本博客将介绍队列的相关知识,包括基于数组的普通队列,基于链表的普通队列,基于数组的双端队列,基于链表的双端队列,但不包括优先级队列(PriorityQueue),此数据结构将单独发一篇博客,那么,让我们从基础的队列开始吧!

一.队列概述

1.队列概述

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

可以把他想象成排队买东西,先排队先买。

特性:FIFO(First In First Out),先进先出。

Quque在Java集合框架中的位置如下:

虽然看起来很单一,但实际上队列的实现相当丰富。

二.队列的实现

我们要模拟实现是基于数组的循环队列、基于链表的单端队列、基于数组的双端队列、基于链表的双端队列。

1.源码简介

先看看源码(可以直接看模拟实现):
首先看看Queue接口:

继承自collection接口,提供了一些队列基本方法,

还有继承自Queue的Deque接口:

接下来看看AbstractQueue:

接下来是基本实现类:

有ArrayQueue:

LinkedList,没错,LinkedList也可以被当成队列来使用:

LinkedList是继承自Deque的,除此之外,还有像PriorityQueue(优先级队列)、BlockingDeque(双端阻塞队列)等我们暂且不讨论。

2.基于数组的循环队列

(1)原理

为什么会加上循环特性呢?

原因是普通非循环队列重复使用会造成严重的内存浪费,而加上循环特性后,就可以避免这一情况了,请看如下情况:

1、2出队,5、6、7、8入队后:

由于使用的是逻辑删除,所以1、2所占内存其实还在,反复增删后(内存不足自动扩容),就会变成一个有效数据极少,所占内存极大的队列,与是,改进成循环队列:

还是这张图,但是当我们进行4、5出队,9、10、11、12入队的操作后,就开始循环存储:

有效区域为6~12,当继续添加元素13~20后,判断队尾会追上队头时,容量不足,于是会先把所有数据复制到内存扩大的新数组,并从0下标按顺序排列后才会继续添加元素:

先复制:

再添加元素: 

通常我们会用size与capacity来记录容量是否满出,即size>=capacity时自动扩容。

在设计时,要特别注意%处的操作。

(2)基本结构

public class MyQueue {private int[] queue;   // 存储元素的数组private int front;     // 队头指针private int size;      // 当前元素数量private int capacity;  // 当前队列容量private static final int DEFAULT_CAPACITY = 10;// 初始化队列public MyQueue() {this.capacity = DEFAULT_CAPACITY;this.queue = new int[capacity];this.front = 0;this.size = 0;}......

说明:队尾指针为 rear = (front + size) % capacity ,可以通过计算求得,不需要定义一个。

或者定义 rear,不定义size,都可以,此处队尾指针表示的是队尾元素的下一个节点,方便插入。

(3)扩容

// 队列扩容
private void resize(int newCapacity) {int[] newArray = new int[newCapacity];// 将旧数组元素按顺序复制到新数组头部for (int i = 0; i < size; i++) {newArray[i] = queue[(front + i) % capacity];}queue = newArray;front = 0;       // 重置队头指针capacity = newCapacity; // 更新容量
}

(4)enqueue(入队)

// 入队操作(支持自动扩容)
public void enqueue(int item) {// 队列已满时触发扩容if (isFull()) {resize(capacity * 2); // 容量翻倍}int rear = (front + size) % capacity;queue[rear] = item;size++;
}

(5)dequeue(出队)

// 出队操作
public int dequeue() {if (isEmpty()) {throw new IllegalStateException("队列为空,无法出队");}int item = queue[front];front = (front + 1) % capacity;size--;return item;
}

(6)peek(查看队头)

// 查看队头元素
public int peek() {if (isEmpty()) {throw new IllegalStateException("队列为空");}return queue[front];
}

(7)其他

// 判断队列是否为空
public boolean isEmpty() {return size == 0;
}// 判断队列是否已满
public boolean isFull() {return size == capacity;
}// 获取当前队列容量(测试用)
public int getCapacity() {return capacity;
}

(8)完整实现及测试代码

为了测试,初始容量改为3:

public class MyQueue {private int[] queue;   // 存储元素的数组private int front;     // 队头指针private int size;      // 当前元素数量private int capacity;  // 当前队列容量(不再是final)private static final int DEFAULT_CAPACITY = 3;// 初始化队列public MyQueue() {this.capacity = DEFAULT_CAPACITY;this.queue = new int[capacity];this.front = 0;this.size = 0;}// 入队操作(支持自动扩容)public void enqueue(int item) {// 队列已满时触发扩容if (isFull()) {resize(capacity * 2); // 容量翻倍}int rear = (front + size) % capacity;queue[rear] = item;size++;}// 出队操作public int dequeue() {if (isEmpty()) {throw new IllegalStateException("队列为空,无法出队");}int item = queue[front];front = (front + 1) % capacity;size--;return item;}// 查看队头元素public int peek() {if (isEmpty()) {throw new IllegalStateException("队列为空");}return queue[front];}// 队列扩容private void resize(int newCapacity) {int[] newArray = new int[newCapacity];// 将旧数组元素按顺序复制到新数组头部for (int i = 0; i < size; i++) {newArray[i] = queue[(front + i) % capacity];}queue = newArray;front = 0;       // 重置队头指针capacity = newCapacity; // 更新容量}// 判断队列是否为空public boolean isEmpty() {return size == 0;}// 判断队列是否已满public boolean isFull() {return size == capacity;}// 获取当前队列容量(测试用)public int getCapacity() {return capacity;}// 测试代码public static void main(String[] args) {MyQueue queue = new MyQueue();// 初始容量3queue.enqueue(10);queue.enqueue(20);queue.enqueue(30);System.out.println("当前容量: " + queue.getCapacity()); // 3// 触发扩容到6queue.enqueue(40);System.out.println("扩容后容量: " + queue.getCapacity()); // 6// 继续填充测试queue.enqueue(50);queue.enqueue(60);queue.enqueue(70); // 再次触发扩容到12System.out.println("再次扩容后容量: " + queue.getCapacity()); // 12// 验证出队顺序System.out.print("出队顺序: ");while (!queue.isEmpty()) {System.out.print(queue.dequeue() + " "); // 10 20 30 40 50 60 70}}
}

3.基于链表的单端队列

(1)原理

由于我们只对头尾进行操作,所以我们可以用链表来实现,头删对应出队,尾差对应入队。

由于链式结构的存在,不必担心内存不足与内存浪费,当然,一般情况下,由于创建多个对象节点,其效率不如数组实现高。

(2)实现

由于与链表类似,我们直接给出完整实现及测试用例:
 

public class LinkedListQueue {// 链表节点定义private static class Node {int data;Node next;Node(int data) {this.data = data;this.next = null;}}private Node front; // 队头指针(出队位置)private Node rear;  // 队尾指针(入队位置)private int size;   // 队列元素数量public LinkedListQueue() {front = null;rear = null;size = 0;}// 入队操作(尾插)public void enqueue(int item) {Node newNode = new Node(item);if (isEmpty()) {// 队列为空时,front和rear都指向新节点front = newNode;rear = newNode;} else {// 非空时,将新节点链接到队尾,并更新rearrear.next = newNode;rear = newNode;}size++;}// 出队操作(头删)public int dequeue() {if (isEmpty()) {throw new IllegalStateException("队列为空,无法出队");}int item = front.data;front = front.next; // 移动队头指针size--;// 如果出队后队列为空,需要同时更新rear为nullif (isEmpty()) {rear = null;}return item;}// 查看队头元素public int peek() {if (isEmpty()) {throw new IllegalStateException("队列为空");}return front.data;}// 判断队列是否为空public boolean isEmpty() {return front == null;}// 获取队列元素数量public int size() {return size;}// 测试代码public static void main(String[] args) {LinkedListQueue queue = new LinkedListQueue();// 入队测试queue.enqueue(10);queue.enqueue(20);queue.enqueue(30);System.out.println("入队后队列大小: " + queue.size()); // 3// 查看队头System.out.println("当前队头: " + queue.peek()); // 10// 出队测试System.out.println("出队: " + queue.dequeue()); // 10System.out.println("出队: " + queue.dequeue()); // 20System.out.println("出队后剩余大小: " + queue.size()); // 1// 再次入队queue.enqueue(40);System.out.println("新队尾元素: " + queue.rear.data); // 40// 清空队列测试System.out.println("出队: " + queue.dequeue()); // 30System.out.println("出队: " + queue.dequeue()); // 40System.out.println("队列是否为空: " + queue.isEmpty()); // true// 测试空队列异常try {queue.dequeue();} catch (IllegalStateException e) {System.out.println("异常捕获: " + e.getMessage()); // 队列为空,无法出队}}
}

4.双端队列

(1)原理

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。 那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。

其两端都可出入的特性给了Deque很大的灵活性,使其基本代替了Stack,成为栈的主要实现类。

其也是实现滑动窗口的首选数据结构。

由于实现基本同上,我们直接给出实现及测试用例。

(2)基于数组的双端队列

public class MyArrayDeque {private int[] data;private int front;   // 队头指针private int size;    // 当前元素数量private int capacity; // 当前容量private static final int DEFAULT_CAPACITY = 8;// 初始化队列(默认容量8)public MyArrayDeque() {this(DEFAULT_CAPACITY);}public MyArrayDeque(int initialCapacity) {capacity = initialCapacity;data = new int[capacity];front = 0;size = 0;}// 队头插入元素public void addFirst(int item) {if (isFull()) resize(capacity * 2);front = (front - 1 + capacity) % capacity; // 向前移动队头指针data[front] = item;size++;}// 队尾插入元素public void addLast(int item) {if (isFull()) resize(capacity * 2);int rear = (front + size) % capacity;data[rear] = item;size++;}// 移除队头元素public int removeFirst() {if (isEmpty()) throw new IllegalStateException("Deque is empty");int item = data[front];front = (front + 1) % capacity;size--;return item;}// 移除队尾元素public int removeLast() {if (isEmpty()) throw new IllegalStateException("Deque is empty");int rear = (front + size - 1) % capacity;int item = data[rear];size--;return item;}// 查看队头元素public int peekFirst() {if (isEmpty()) throw new IllegalStateException("Deque is empty");return data[front];}// 查看队尾元素public int peekLast() {if (isEmpty()) throw new IllegalStateException("Deque is empty");return data[(front + size - 1) % capacity];}// 动态扩容private void resize(int newCapacity) {int[] newData = new int[newCapacity];// 将元素按顺序复制到新数组头部for (int i = 0; i < size; i++) {newData[i] = data[(front + i) % capacity];}data = newData;capacity = newCapacity;front = 0; // 重置队头指针}public boolean isEmpty() {return size == 0;}public boolean isFull() {return size == capacity;}public int size() {return size;}// 测试代码public static void main(String[] args) {MyArrayDeque deque = new MyArrayDeque(3);// 队头插入deque.addFirst(10);deque.addFirst(20);deque.addFirst(30); // 触发扩容到6// 队尾插入deque.addLast(40);deque.addLast(50);deque.addLast(60); // 再次触发扩容到12// 验证顺序System.out.println("队头移除: " + deque.removeFirst()); // 30System.out.println("队尾移除: " + deque.removeLast());   // 60System.out.println("当前队头: " + deque.peekFirst());    // 20System.out.println("当前队尾: " + deque.peekLast());     // 50}
}

(3)基于链表的双端队列

public class LinkedDeque {// 双向链表节点定义private static class Node {int data;Node prev;Node next;Node(int data) {this.data = data;this.prev = null;this.next = null;}}private Node front;  // 队头指针private Node rear;   // 队尾指针private int size;    // 队列元素数量public LinkedDeque() {front = null;rear = null;size = 0;}// 队头插入元素public void addFirst(int item) {Node newNode = new Node(item);if (isEmpty()) {// 队列为空时,front和rear都指向新节点front = rear = newNode;} else {// 将新节点链接到当前队头前newNode.next = front;front.prev = newNode;front = newNode; // 更新队头指针}size++;}// 队尾插入元素public void addLast(int item) {Node newNode = new Node(item);if (isEmpty()) {front = rear = newNode;} else {// 将新节点链接到当前队尾后rear.next = newNode;newNode.prev = rear;rear = newNode; // 更新队尾指针}size++;}// 移除队头元素public int removeFirst() {if (isEmpty()) {throw new IllegalStateException("队列为空,无法移除");}int item = front.data;if (front == rear) {// 只有一个元素时,移除后队列为空front = rear = null;} else {front = front.next;front.prev = null; // 断开旧队头链接}size--;return item;}// 移除队尾元素public int removeLast() {if (isEmpty()) {throw new IllegalStateException("队列为空,无法移除");}int item = rear.data;if (front == rear) {front = rear = null;} else {rear = rear.prev;rear.next = null; // 断开旧队尾链接}size--;return item;}// 查看队头元素public int peekFirst() {if (isEmpty()) {throw new IllegalStateException("队列为空");}return front.data;}// 查看队尾元素public int peekLast() {if (isEmpty()) {throw new IllegalStateException("队列为空");}return rear.data;}public boolean isEmpty() {return size == 0;}public int size() {return size;}// 打印队列内容(测试用)public void display() {Node current = front;System.out.print("队列内容: ");while (current != null) {System.out.print(current.data + " ");current = current.next;}System.out.println();}// 测试代码public static void main(String[] args) {LinkedDeque deque = new LinkedDeque();// 队头插入测试deque.addFirst(10);deque.addFirst(20);deque.display(); // 队列内容: 20 10// 队尾插入测试deque.addLast(30);deque.addLast(40);deque.display(); // 队列内容: 20 10 30 40// 移除测试System.out.println("移除队头: " + deque.removeFirst()); // 20System.out.println("移除队尾: " + deque.removeLast());  // 40deque.display(); // 队列内容: 10 30// 边界测试deque.removeFirst();deque.removeLast();System.out.println("队列是否为空: " + deque.isEmpty()); // true// 异常测试try {deque.removeFirst();} catch (IllegalStateException e) {System.out.println("异常捕获: " + e.getMessage());}}
}

5.其他队列

  • 优先级队列(PriorityQueue):其底层实现是堆,具体来说是完全二叉树,我们会单独开一篇博客分析。
  • 阻塞队列(BlockingDeque):用于线程操作的数据结构。

三.队列API的使用

Java当中基础队列有2种实现方式:

  • ArrayDeque
  • LinkedList

一般两者可以相互替代,只是LinkedList适用于需要频繁增删的队列,ArrayDeque适用于对访问性能要求高的队列,一般我们用ArrayDeque。

本博客重点在于理解其实现以及原理,详细使用请详见:Java ArrayDeque - Java教程 - 菜鸟教程

以及官方文档:ArrayDeque (Java Platform SE 8 )

结语

队列还是比较好理解和实现的,在理解了数据结构的底层原理以及实现后,我们才能更加清晰地正确使用数据结构,也能更好地学习后面的算法和原理了。

好了,本博客到此结束,喜欢不妨点个赞吧,我接下来会把剩下的数据结构一一解析完,下次是二叉树,敬请期待!

我们下次见ξ( ✿>◡❛)!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/39132.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习Python编程:从入门到工程实践

第一章 Python语言概述与生态体系 1.3 Python在工业界的应用场景 # 示例:使用FastAPI构建RESTful接口 from fastapi import FastAPI from pydantic import BaseModelapp = FastAPI()class Item(BaseModel):name: strprice: float@app.post("/items/") async def cr…

Flutter 学习之旅 之 flutter 使用 connectivity_plus 进行网路状态监听(断网/网络恢复事件监听)

Flutter 学习之旅 之 flutter 使用 connectivity_plus 进行网路状态监听&#xff08;断网/网络恢复事件监听&#xff09; 目录 Flutter 学习之旅 之 flutter 使用 connectivity_plus 进行网路状态监听&#xff08;断网/网络恢复事件监听&#xff09; 一、简单介绍 二、conne…

matlab的meshgrid

文章目录 一、什么是 meshgrid&#xff1f;二、基本语法三、为什么需要 meshgrid&#xff1f;四、meshgrid 与 ndgrid 的区别 一、什么是 meshgrid&#xff1f; meshgrid 是 MATLAB 中用于生成网格点坐标矩阵的函数&#xff0c;常用于三维绘图&#xff08;如 surf, mesh, cont…

word插入Mathtype公式居中和自动更新

word插入公式自动更新 前提&#xff1a;安装Mathtype 1.word中查看页的宽度 出现如下 2.设置样式 出现这个窗口 给样式随便起个名字 3.修改样式 3.1 设置两个制表位 第二个 3.2 修改公式字体 如下所示 4. 修改公式格式 4.1在word中打开 Mathtype 4.2 修改公式的格式 变成…

用 pytorch 从零开始创建大语言模型(六):对分类进行微调

用 pytorch 从零开始创建大语言模型&#xff08;六&#xff09;&#xff1a;对分类进行微调 6 微调用于分类6.1 微调的不同类别6.2 准备数据集6.3 创建数据加载器6.4 使用预训练权重初始化模型6.5 添加分类头部6.6 计算分类损失和准确率6.7 在监督数据上微调模型6.8 使用LLM进…

阿里云对象存储教程

搜“对象存储->免费试用” 选择你的心仪产品&#xff0c;我使用的是第一个 创建后获得三个实例&#xff1a; 点击右上角自己的账号可以进入到AccessKey管理界面 回到对象存储控制台创建Bucket实例 在以下文件中替换自己Bucket的信息即可美美使用~ package com.kitty.blog…

基于Python的智慧金融风控系统的设计与实现

指导途径&#xff08;&#x1f6f0;&#xff09;&#xff1a;NzqDssm16 1立题依据 1.1毕业论文&#xff08;设计&#xff09;的研究背景 随着金融行业数字化转型加速&#xff0c;智能风控系统成为防范金融风险的核心支撑。传统风控手段存在数据处理效率低下、模型更新滞后、人…

分布式算法:Paxos Raft 两种共识算法

1. Paxos算法 Paxos算法是 Leslie Lamport&#xff08;莱斯利兰伯特&#xff09;在 1990 年提出的一种分布式系统共识算法。也是第一个被证明完备的共识算法&#xff08;前提是不存在恶意节点&#xff09;。 1.1 简介 Paxos算法是第一个被证明完备的分布式系统共识算法。共识…

Day20-前端Web案例——部门管理

目录 部门管理1. 前后端分离开发2. 准备工作2.1 创建Vue项目2.2 安装依赖2.3 精简项目 3. 页面布局3.1 介绍3.2 整体布局3.3 左侧菜单 4. Vue Router4.1 介绍4.2 入门4.3 案例4.4 首页制作 5. 部门管理5.1部门列表5.1.1. 基本布局5.1.2 加载数据5.1.3 程序优化 5.2 新增部门5.3…

信创-人大金仓数据库创建

一. 官文 资源下载地址 https://download.kingbase.com.cn/xzzx/index.htm 下载安装文件 下载授权文件 产品文档地址&#xff1a;https://help.kingbase.com.cn/v8/index.html 二. 概念 2.1 体系结构 ‌ 实例结构 ‌&#xff1a;由数据库文件和 KingbaseES 实例组成。数据…

[ACTF2020 新生赛]BackupFile-3.23BUUCTF练习day5(1)

[ACTF2020 新生赛]BackupFile-3.23BUUCTF练习day5(1) 解题过程 打开题目环境 看题目意思应该是让我找备份文件 备份文件一般的后缀名为 .rar .zip .7z .tar.gz .bak .swp .txt .html .bak 直接扫描一下 在url中输入/index.php.bak 弱类型比较 为弱相等&#xff0c;即当…

【嵌入式Linux】基于ArmLinux的智能垃圾分类系统项目

目录 1. 功能需求2. Python基础2.1 特点2.2 Python基础知识2.3 dict嵌套简单说明 3. C语言调用Python3.1 搭建编译环境3.2 直接调用python语句3.3 调用无参python函数3.4 调用有参python函数 4. 阿里云垃圾识别方案4.1 接入阿里云4.2 C语言调用阿里云Python接口 5. 香橙派使用摄…

css的背景

css背景属性&#xff0c;可以给页面元素添加背景样式。 一.背景颜色 二.背景图片 语法 backgroud-image &#xff1a;none || url(图像地址) 三.背景平铺 既可以添加背景颜色也可以添加背景图片&#xff0c;只不过背景图片会压住背景颜色 四.背景位置 1.方位名词 如果只指定…

macOS Sequoia 15.3 一直弹出“xx正在访问你的屏幕”

&#x1f645; 问题描述 macOS 系统升级后&#xff08;15.2或者15.3均出现过此问题&#xff09;&#xff0c;不管是截图还是开腾讯会议&#xff0c;只要跟捕捉屏幕有关&#xff0c;都一直弹出这个选项&#xff0c;而且所有软件我都允许访问屏幕了&#xff0c;这个不是询问是否…

高德终端技术总结:高可用架构如何练成?

前言 高德地图作为国民级应用&#xff0c;特别是出行场景的独特性&#xff0c;要确保在线导航高并发和交通安全级的超稳定性&#xff0c;这对技术团队提出异乎寻常的高要求&#xff0c;无论是终端、云端&#xff0c;还是“终端-云端”之间的连接&#xff0c;都必须实现“高可用…

UDP套接字编程(代码)

什么是socket套接字编程&#xff1f; 通过Ip地址 端口号这种方式定位一台主机&#xff0c;这样的方式我们就叫做socket套接字。 Udp Socket 接口介绍 这些案列我们使用的接口基本都是一样的&#xff0c;所以在这里我先把接口介绍完&#xff0c;具体的细节后面在说明。 创…

C# 调用 VITS,推理模型 将文字转wav音频net8.0 跨平台

一、系统环境 操作系统&#xff1a;win10&#xff0c;win11 运行环境&#xff1a;dotnet8 工具:命令行&#xff0c;powershell 开源库:sherpa-onnx 二、工具和源码下载 开源库:https://k2-fsa.github.io/sherpa/onnx/index.html 运行环境下载 https://dotnet.microsoft.c…

【AI学习笔记】Coze平台实现将Excel文档批量导入数据库全过程

背景前摇&原视频教程&#xff1a; 最近看到很多同学都在用Coze平台操作数据&#xff0c;我也想了解一下工作流的搭建和数据处理过程&#xff0c;但是一下子又看不懂太复杂的逻辑&#xff0c;于是上B站搜索相关的基础教程。 Coze官方教程&#xff1a; 之前有看过Coze平台…

Certd自动化申请和部署SSL证书并配置https

服务器使用的华为云&#xff0c;之前SSL证书通过配置Cloudflare的DNS实现的&#xff0c;最近华为云备案提示需修改解析至境内华为云IP&#xff0c;若解析境外IP&#xff0c;域名无需备案&#xff0c;需注销或取消接入备案信息&#xff0c;改为使用Certd自搭建证书管理工具&…

AI基础01-文本数据采集

本篇文章是学习文本数据的采集&#xff0c;作为人工智能训练师或者数据分析师有时需要先获取数据&#xff0c;然后进行数据清洗、数据标注。很明显数据采集是后续步骤的基础。 1&#xff09;数据采集定义 数据采集&#xff1a;data acquisition&#xff0c;DAQ 又称为数据获取…