java 数据队列_Java 数据结构 - 队列

Java 数据结构 - 队列

我们今天要讲的数据结构是队列,比如 Java 线程池任务就是队列实现的。

1. 什么是队列

和栈一样,队列也是一种操作受限的线性结构。使用队列时,在一端插入元素,而在另一端删除元素。

5c0a34b9c704f00458b5d88dd36a055d.png

1.1 队列的主要特性

队列中的数据元素遵守 "先进先出"(First In First Out)的原则,简称 FIFO 结构。

限定只能在队列一端插入,而在另一端进行删除操作。

1.2 队列的相关概念

入队(enqueue):队列的插入操作。

出队(dequeue):队列的删除操作。

2. 复杂度分析

和栈一样,队列也有两种实现方案,我们简单分析一下这两种队列的复杂度:

动态队列(链表):也叫链式队列。其插入、删除时间复杂度都是 O(1)。

静态队列(数组):也叫顺序队列。当队列队尾指针移到最后时,此时有两种操作:一是进行简单的数据搬移,二是进行队列循环。

关于队列的实现,我们只实现如下的基本操作。

public interface Queue {

Queue enqueue(Object obj); // 入队

Object dequeue(); // 出队

int size(); // 元素个数

}

2.1 链式队列

链式队列的实现非常简单,其插入和删除的时间复杂度都是 O(1)。为了简化代码的实现,我们引入哨兵结点。如下图所示,head 头结点是哨兵结点,不保存任何数据,从 head.next 开始保存数据,tail 结点指向最后一个元素结点。链表队列头结点和尾结点说明:

头结点:head 结点为哨兵结点,不保存任何数据,数据从第二个结点开始。

尾结点:tail 结点指向最后一个数据结点。

d65cee1a70c3b8db30a007138d9f35cb.png

根据上图,我们可以轻松实现一个链表组成的队列,代码也很简单。

public class LinkedQueue implements Queue {

private Node head;

private Node tail;

private int size;

public LinkedQueue() {

head = new Node(null, null);

tail = head;

}

@Override

public Queue enqueue(Object obj) {

tail.next = new Node(obj, null);

tail = tail.next;

size++;

return this;

}

@Override

public Object dequeue() {

Node next = head.next;

if (next == null) {

return null;

}

head = head.next;

size--;

return next.item;

}

@Override

public int size() {

return size;

}

public static class Node {

private Object item;

private Node next;

public Node(Object item, Node next) {

this.item = item;

this.next = next;

}

}

}

2.2 顺序队列

如果是数组实现的队列,则比链表要复杂一些,当尾结点指向数组的最后一个位置时,没有剩余的空间存放数据时,此时该如何处理?通过我们有两种解决方案:

数据搬移:将 head ~ tail 结点的数据搬移到从 0 结点开始。

循环队列:tail 结点从 0 开始循环使用,不用搬移数据。

我们先看一下循环队列

9df1932c484c12ec473dee5c81af11f4.png

如上图所示,当尾结点指向数组最后一个位置,当 tail 指向数组最后位置时,触发数据搬移,将 head ~ tail 结点的数据搬移到从 0 结点开始。数组队列头结点和尾结点说明:

头结点:head 结点指向第一个数据结点。当 head == tail 时说明队列处于队空状态,直接返回 null。

尾结点:tail 结点指向最后一个数据之后的空结点。当 tail == capcity 时说明队列处于队满状态,需要扩容或进行数据搬移。

根据上述理论代码实现如下:

public class ArrayQueue implements Queue {

private Object[] array;

private int capcity;

// head指向第一个数据结点的位置,tail指向最后一个数据结点之后的位置

private int head;

private int tail;

public ArrayQueue() {

this.capcity = 1024;

this.array = new Object[this.capcity];

}

public ArrayQueue(int capcity) {

this.capcity = capcity;

this.array = new Object[capcity];

}

/**

* tail 指向数组最后位置时,需要触发扩容或数组搬移

* 1. head!=0 说明数组还有剩余的空间,将 head 搬运到队列 array[0]

* 2. head==0 说明数组没有剩余的空间,扩容

*/

@Override

public Queue enqueue(Object obj) {

if (tail == capcity) {

if (head == 0) {

resize();

} else {

rewind();

}

}

array[tail++] = obj;

return this;

}

@Override

public Object dequeue() {

if (head == tail) {

return null;

}

Object obj = array[head];

array[head] = null;

head++;

return obj;

}

// 将 head 搬运到队列 array[0]

private void rewind() {

for (int i = head; i < tail; i++) {

array[i - head] = array[i];

array[i] = null;

}

tail -= head;

head = 0;

}

// 扩容

private void resize() {

int oldCapcity = this.capcity;

int newCapcity = this.capcity * 2;

Object[] newArray = new Object[newCapcity];

for (int i = 0; i < oldCapcity; i++) {

newArray[i] = array[i];

}

this.capcity = newCapcity;

this.array = newArray;

}

@Override

public int size() {

return tail - head;

}

}

说明: 数组队列出队的时间复杂度始终是 O(1)。但入队时要分为三种情况:

有空间:大多数情况,也是最好时间复杂度 O(1)。

没有空间需要数据搬移:执行 n 后触发一次数据搬移,最坏时间复杂度 O(n)。

没有空间需要扩容:执行 n 后触发一次数据搬移,最坏时间复杂度 O(n)。

如果采用摊还分析法,最好时间复杂度 O(1),最坏时间复杂度 O(n),摊还时间复杂度为 O(1)。虽然,平均时间复杂度还是 O(1),但我们能不能不进行数据搬移,直接循环使用数组呢?

2.3 循环队列

循环队列是一种非常高效的队列,我们需要重点掌握它,要能轻松写出无 BUG 的循环队列。

b4b4a2c1ea8705e012d3435555935b2f.png

数组队列头结点和尾结点说明:

头结点:head 结点指向第一个数据结点。当 head == tail 时说明队列处于队空状态,直接返回 null。否则在元素出队后,需要重新计算 head 值。

尾结点:tail 结点指向最后一个数据之后的空结点。每次插入元素后重新计算 tail 值,当 tail == head 时说明队列处于队满状态,需要扩容。

元素位置:对数组长度取模 (tail + 1) % length ,所以这种数据为了提高效率,都要求数组长度为 2^n,通过位运算取模 (tail + 1) & (length - 1)。

public class ArrayCircularQueue implements Queue {

private Object[] array;

private int capcity;

// 头结点指向

private int head;

private int tail;

public ArrayCircularQueue() {

this.capcity = 1024;

this.array = new Object[this.capcity];

}

public ArrayCircularQueue(int capcity) {

this.capcity = capcity;

this.array = new Object[capcity];

}

@Override

public Queue enqueue(Object obj) {

array[tail] = obj;

if ((tail = (tail + 1) % capcity) == head) {

resize();

}

return this;

}

@Override

public Object dequeue() {

if (head == tail) {

return null;

}

Object obj = array[head];

array[head] = null;

head = (head + 1) % capcity;

return obj;

}

// 不扩容,要先判断能否往数组中添加元素

public Queue enqueue2(Object obj) {

if ((tail + 1) % capcity == head) return this;

array[tail] = obj;

tail = (tail + 1) % capcity;

return this;

}

// 扩容

private void resize() {

// 说明还有空间

if (head != tail) {

return;

}

int oldCapcity = this.capcity;

int newCapcity = this.capcity * 2;

Object[] newArray = new Object[newCapcity];

for (int i = head; i < oldCapcity; i++) {

newArray[i - head] = array[i];

}

for (int i = 0; i < head; i++) {

newArray[capcity - head + i] = array[i];

}

this.capcity = newCapcity;

this.array = newArray;

this.head = 0;

this.tail = oldCapcity;

}

@Override

public int size() {

return tail - head;

}

}

说明: 循环队列关键是判断队空和空满的状态分别进行处理。除开扩容操作,循环队列的入队和出队的时间复杂度都是 O(1),同时也可以充分利用 CPU 缓存,所以说一种高效的数据结构。

2.4 阻塞队列和并发队列

3. 队列在软件工程中应用

如 JDK 线程池,当线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?各种处理策略又是如何实现的呢?我们一般有两种处理策略。

非阻塞的处理方式,直接拒绝任务请求;

阻塞的处理方式,将请求排队,等到有空闲线程时,取出排队的请求继续处理。

每天用心记录一点点。内容也许不重要,但习惯很重要!

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

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

相关文章

java+set+split_阿里资深工程师教你如何优化 Java 代码!

原标题&#xff1a;阿里资深工程师教你如何优化 Java 代码&#xff01;作者 | 王超责编 | 伍杏玲明代王阳明先生在《传习录》谈为学之道时说&#xff1a;私欲日生&#xff0c;如地上尘&#xff0c;一日不扫&#xff0c;便又有一层。着实用功&#xff0c;便见道无终穷&#xff0…

myVariable是java标识符吗_java 标识符与变量

一、Java 标识符三要素1.标识符由字母、下划线(_)、美元符号($)或者字母组成。2.标识符应以字母、下划线(_)、美元符开头。3.标识符字符大小写敏感&#xff0c;长度无限制。标识符最重要的就是 见名知意并且不能与java关键字重名!二、Java 变量1.java变量是程序中最基本的单元。…

友盟统计java代码_SFAnalytics 分析友盟统计源码,反编译 SDK,还有部分没有 出来 android 259万源代码下载- www.pudn.com...

文件名称: SFAnalytics下载 收藏√ [5 4 3 2 1 ]开发工具: Java文件大小: 8023 KB上传时间: 2016-06-05下载次数: 0提 供 者: 花心大萝卜详细说明&#xff1a;分析友盟统计源码&#xff0c;反编译友盟统计SDK&#xff0c;还有部分没有反编译出来-Analysis their Allies s…

java mvc数据库 封装_关于SpringMvc参数封装_JavaEE框架(Maven+SpringMvc+Spring+MyBatis)全程实战教程_Java视频-51CTO学院...

SpringMVCSpring MVC属于SpringFrameWork的后续产品&#xff0c;已经融合在Spring Web Flow里面。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色&#xff0c;这种分离让它们更容易进行定制。SpringSpring是一个开源框架&#xff0c;Spring是于2003 年兴起的…

20199计算机二级java答案_计算机二级Java练习题-2019.9

是不是急于做大量的计算机等级考试题库&#xff0c;却因测试结果不尽人意而心慌不安&#xff1f;不要急&#xff01;考无忧小编为大家准备了一些二级Java练习题&#xff0c;希望能帮助大家高效复习&#xff0c;轻松通关&#xff01;1.下列叙述中正确的是()。A.栈是“先进先出”…

平行志愿遵循分数优先php,2020平行志愿的录取规则是什么有哪些优势

摘要&#xff1a; 2020平行志愿的录取规则是什么有哪些优势为你介绍平行志愿的投档原则仍为“分数优先&#xff0c;遵循志愿”&#xff0c;而传统的顺序志愿则是根据“志愿优先&#xff0c;按分排序”。 2020平行志愿录取规则 平行志愿的投档原则仍为“分数优先&#xff0c;遵循…

Oracle quartile函数,Oracle分析函数六——数据分布函数及报表函数

Oracle 分析函数——数据分布函数及报表 函数CUME_DIST功能描述&#xff1a;计算一行在组中的相对位置&#xff0c; CUME_DIST 总是返回大于 0 、小于或等于 1 的数&#xff0c;该数表示该行在 N 行中的位置。例如&#xff0c;在一个 3 行的组中&#xff0c;返回的累计分布值为…

redhat配置oracle yum源,Redhat5和6 YUM源配置的区别

一、概述随着各个软件版本的不断升级&#xff0c;我们需要掌握的软件特性也越来越多&#xff0c;技术的不断更新也促进了我们脑细胞的循环。 今天在配置RedHat6.3的yum源时也出现了一个有趣的事情&#xff0c;下面请听俺道来。二、问题处理问题源于哥们处&#xff0c;自己就搭建…

linux7 修改服务启动项目命令,centos7服务部署flask项目

8种机械键盘轴体对比本人程序员&#xff0c;要买一个写代码的键盘&#xff0c;请问红轴和茶轴怎么选&#xff1f;centos7语言配置问题登录服务器&#xff0c;注意观察是否出现以下警告提示warning: setlocale: LC_CTYPE: cannot change locale (en_US.utf8): No such file or d…

虚拟机linux中怎样打开qt,虚拟机中在Centos 4.7中安装qt-x11-opensource-4.4.3

本人最近学习qt&#xff0c;把qt安装在了虚拟机下&#xff0c;用的是免费的Linux&#xff0c;qt是Linux下开发c不错的软件&#xff0c;经过了2天的奋战&#xff0c;终于把qt安装在了自己的机器上面&#xff0c;现在把安装步骤告诉大家&#xff0c;希望对你们安装错误的同志们有…

linux top p 乱码,将Linux top命令输入到指定文件时的乱码问题

根据需求&#xff0c;项目需要读取linux系统环境参数&#xff0c;所以需要将top命令的输出结果输入到文件中。用重定向命令如下&#xff1a;topresult.txt。用vi打开文件&#xff1a;viresult.txt。显示的根据需求&#xff0c;项目需要读取linux系统环境参数&#xff0c;所以需…

win10子linux如何重置密码,Win10如何使用Windows PowerShell命令重置本地账户密码

在win10系统中&#xff0c;都自带有本地账户&#xff0c;为了安全也会给本地商户设置密码&#xff0c;不过使用一段时间之后&#xff0c;想要重置本地账户密码&#xff0c;但是很多用户并不知道要如何操作&#xff0c;其实我们可以使用Windows PowerShell命令方式来实现&#x…

linux wine 性能,Wine 1.9.16 发布,改善 GDI 性能

Wine 1.9.16 最近发布下载&#xff0c;配备了大量的新功能&#xff0c;改进和错误修正。显着的更新包括在MacOS下能更好与64位二进制兼容性&#xff0c;提升JavaScript性能&#xff0c;对Direct3D的改进&#xff0c;在Direct3D更多的着色器指令&#xff0c;提升GDI性能&#xf…

二级考试c语言中 星号与字母 题型总结,2017年计算机二级考试C语言知识点归纳...

2017年计算机二级考试C语言知识点归纳计算机二级考试是全国计算机等级考试(National Computer Rank Examination&#xff0c;简称NCRE)四个等级中的一个等级&#xff0c;考核计算机基础知识和使用一种高级计算机语言编写程序以及上机调试的基本技能。下面是2017年计算机二级考试…

c#语言输出字符串长度,C#获取字符串长度(string.Length)

字符串实际上是由多个字符组成的&#xff0c;字符串中的第一个字符使用字符串[0]即可得。[0]中的0称为下标。获取字符串中的第一个字符使用的下标是0,则字符串中最后一个字符的下标是字符串的长度减1。使用C#语言如果要获取字符串的长度&#xff0c;使用Length属性即可&#xf…

易语言取c盘文件夹中的文件被占用,win7系统怎么快速找出文件被什么进程占用|win7解除文件占用的方法...

‍‍很多时候我们想删除文件或文件夹的时候&#xff0c;会出现文件正在使用的提示导致文件无法删除&#xff0c;如果知道是什么程序在使用这个文件好&#xff0c;但多数时候我们并不知道是被什么程序占用&#xff0c;那么在win7系统怎么快速找出文件被什么进程占用呢&#xff1…

菜鸟linux的sed,Linux sed命令菜鸟入门

本文首先以一个简单的sed命令应用示例&#xff0c;说明如何使用sed命令进行文本处理&#xff0c;接着从sed命令的用法、工作原理、行地址模式以及sed命令支持的脚本命令详细介绍sed命令的用法。一个完整的sed命令使用简单示例现在有一个待处理文件test.txt&#xff0c;文件中包…

三星s10能升级android11,三星 S10+手机已在测试 Android 11 系统

IT之家2月25日消息 谷歌本月初发布了首个Android 11开发者预览版&#xff0c;首先面向Pixel手机&#xff0c;不过看起来三星已经在Galaxy S10 手机开始测试最新系统。IT之家从Geekbench数据库中获知&#xff0c;上面出现了运行Android R&#xff0c;型号为SM-G975F的三星Galaxy…

android 5. 蓝牙 mesh,蓝牙mesh组网

智能照明是智能家居的一个重要入口&#xff0c;传统照明方案存在布线复杂&#xff0c;控制单一等问题。搭配飞易通MESH组网模组替换传统方案&#xff0c;无需额外的布线。提供更智能的控制&#xff0c;更极致的用户体验。一、MESH应用领域:蓝牙5.0MESH是由SIG蓝牙联盟发布建立的…

android opencv 银行卡识别,NDK 开发之使用 OpenCV 实现银行卡号识别

前言在日常的开发中&#xff0c;我们有时会遇到添加银行卡的需求&#xff0c;这时候&#xff0c;产品可能会让你仿一下支付宝之类的相机扫描识别银行卡号。很多时候&#xff0c;做这样的需求会去找找稳定的第三方&#xff0c;本文通过 OpenCV 结合识别的需求带你分析如何实现银…