深入理解循环队列----循环数组实现ArrayDeque

news2024/12/24 8:52:18

     我们知道队列这种数据结构的物理实现方式主要还是两种,一种是链队列(自定义节点类),另一种则是使用数组实现,两者各有优势。此处我们将要介绍的循环队列其实是队列的一种具体实现,由于一般的数组实现的队列结构在频繁出队的情况下,会产生假溢出现象,导致数组使用效率降低,所以引入循环队列这种结构。本文将从以下两个大角度介绍循环队列这种数据结构:

  • 循环数组实现循环队列
  • Java中具体实现容器类ArrayDeque

一、循环队列      为了深刻体会到循环队列这个结构优于非循环队列的地方,我们将首先介绍数组实现的非循环队列结构。队列这种数据结构,无论你是用链表实现,还是用数组实现,它都是要有两个指针分别指向队头和队尾。在我们数组的实现方式中,用两个int型变量用于记录队头和队尾的索引。

一个队列的初始状态,head和tail都指向初始位置(索引为0处)。head永远指向该队列的队头元素,tail则指向该队列最后一个元素的下一位置,当有入队操作时:

当有出队操作时:

当遇到出队操作时,head会移向下一元素位置。当然,对于这种方式入队和出队,队空的判断条件显然是head=tail,队满的判断条件是tail=array.length(数组最后一个位置的下一位置)。显然,这种结构最致命的缺陷就是,tail只知道向后移动,一旦到达数组边界就认为队满,但是队列可能时刻在出队,也就是前面元素都出队了,tail也不知道。例如:

此时tail判断队满,我们暂时认为资源利用是可以接受的,但是如果接下来不断发生出队操作:

此时tail依然通过判断,认为队满,不能入队,这时数组的利用率我们是不能接受的,这样浪费很大。所以,我们引入循环队列,tail可以通过mode数组的长度实现回归初始位置,下面我们具体来看一下。

按照我们的想法,一旦tail到达数组边界,那么可以通过与数组长度取模返回初始位置,这种情况下判断队满的条件为tail=head

此时tail的值为8,取模数组长度8得到0,发现head=tail,此时认为队列满员。这是合理的,但是我们忽略了一个重要的点,判断队空的条件也是head=tail,那么该怎么区分是队空还是队满呢?解决办法是,空出队列中一个位置,如果(tail+1)%array.length=head,我们就认为队满,下面说明其合理性。

上面遇到的问题是,tail指向了队尾的后一个位置,也就是新元素将要被插入的位置,如果该位置和head相等了,那么必然说明当前状态已经不能容纳一个元素入队(间接的说明队满)。因为这种情况是和队空的判断条件是一样的,所以我们选择舍弃一个节点位置,tail指向下一个元素的位置,我们使用tail+1判断下一个元素插入之后,是否还能再加入一个元素,如果不能了说明队列满,不能容纳当前元素入队(其实还剩下一个空位置),看图:

tail通过取模,回归到初始位置,我们判断tail+1是否等于head,如果等于说明队满,不允许入队操作,当然这是牺牲了一个节点位置来实现和判断队空的条件进行区分。上述文字基本完成了队循环队列的理论介绍,下面我们看在Java中对该数据结构的具体实现是怎样的。

二、双端队列实现类ArrayDeque      ArrayDeque中主要有以下几个属性域:

transient Object[] elements;
transient int head;
transient int tail;
private static final int MIN_INITIAL_CAPACITY = 8;

复制

elements就是我们上述介绍用于存储队列中每个节点,不过在ArrayDeque中该数组长度是没有限制的,采用一种动态扩容机制实现动态扩充数组容量。head和tail分别代表着头指针和尾指针。MIN_INITIAL_CAPACITY 代表着创建一个队列的最小容量,具体使用情况在下文详细介绍。现在我们看下它的几个构造函数:

public ArrayDeque() {
    elements = new Object[16];
}
public ArrayDeque(int numElements) {
    allocateElements(numElements);
}

复制

如果没有指定显式传入elements的长度,则默认16。如果显式传入一个代表elements的长度的变量,那么会调用allocateElements做一些简单的处理,并不会简单的将你传入的参数用来构建elements,它会获取最接近numElements的2的指数值,比如:numElements等于20,那么elements的长度会为32,numElements为11,那么对应elements的长度为16。但是如果你传入一个小于8的参数,那么会默认使用我们上述介绍的静态属性值作为elements的长度。至于为什么这么做,因为这么做会大大提高我们在入队时候的效率,我们等会儿会看到。

入队操作 由于ArrayDeque实现了Deque,所以它是一个双向队列,支持从头部或者尾部添加节点,由于内部操作类似,我们只简单介绍从尾部添加入队操作。涉及以下一些函数:

public void addLast(E e) {
    if (e == null)
        throw new NullPointerException();
    elements[tail] = e;
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
    doubleCapacity();
}

public boolean offerLast(E e) {
    addLast(e);
    return true;
}

public boolean add(E e) {
    addLast(e);
    return true;
}

复制

显然,主要的方法还是addLast,其实有人可能会疑问,为什么要这么多重复的方法呢?其实,虽然我们这个ArrayDeque它实现了双端队列,并且我们本篇主要把他当做队列来研究,其实该类完全可以作为栈或者一些其他结构来使用,所以提供了一些其他的方法,但本质上还是某几个方法。此处我们主要研究下addLast这个方法,该方法首先将你要添加的元素入队,然后通过这条语句判断队是否已满:

if ( (tail = (tail + 1) & (elements.length - 1)) == head)

复制

这条语句的判断条件还是比较难理解的,我们之前在构造elements元素的时候,说过它的长度一定是2的指数级,所以对于任意一个2的指数级的值减去1之后必然所有位全为1,例如:8-1之后为111,16-1之后1111。而对于tail来说,当tail+1小于等于elements.length - 1,两者与完之后的结果还是tail+1,但是如果tail+1大于elements.length - 1,两者与完之后就为0,回到初始位置。这种判断队列是否满的方式要远远比我们使用符号%直接取模高效,jdk优雅的设计从此可见一瞥。接着,如果队列满,那么会调用方法doubleCapacity扩充容量,

private void doubleCapacity() {
    assert head == tail;
    int p = head;
    int n = elements.length;
    int r = n - p; 
    int newCapacity = n << 1;
    if (newCapacity < 0)
        throw new IllegalStateException("Sorry, deque too big");
    Object[] a = new Object[newCapacity];
    System.arraycopy(elements, p, a, 0, r);
    System.arraycopy(elements, 0, a, r, p);
    elements = a;
    head = 0;
    tail = n;
}

复制

该方法还是比较容易理解的,首先会获取到原数组长度,扩大两倍构建一个空数组,接下来就是将原数组中的内容移动到新数组中,下面通过截图演示两次移动过程:

这是一个满队状态,假如我们现在还需要入队,那么久需要扩容,扩容结果如下:

其实两次移动数组,第一次将head索引之后的所有元素移动到新数组中,第二次将tail到head之间的所有元素移动到新数组中。实际上,就是在移动的时候对原来的顺序进行了调整。对于addFirst只不过是将head向前移动一个位置,然后添加新元素。

出队操作 出队操作和入队一样,具有着多个不同的方法,但是内部调用的还是一个pollFirst方法,我们主要看下该方法的具体实现即可:

public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    E result = (E) elements[h];
    if (result == null)
        return null;
    elements[h] = null;
    head = (h + 1) & (elements.length - 1);
    return result;
}

复制

该方法很简单,直接获取数组头部元素即可,然后head往后移动一个位置。这是出队操作,其实删除操作也是一种出队,内部还是调用了pollFirst方法:

public E removeFirst() {
    E x = pollFirst();
    if (x == null)
        throw new NoSuchElementException();
    return x;
 }

复制

其他的一些操作 我们可以通过getFirst()或者peekFirst()获取队头元素(不删除该元素,只是查看)。toArray方法返回内部元素的数组形式。

public Object[] toArray() {
    return copyElements(new Object[size()]);
}

复制

还有一些利用索引或者值来检索具体节点的方法,由于这些操作并不是ArrayDeque的优势,此处不再赘述了。

至此,有关ArrayDeque的简单原理已经介该绍完了,ArrayDeque的主要优势在于尾部添加元素,头部出队元素的效率是比较高的,内部使用位操作来判断队满条件,效率相对有所提高,并且该结构使用动态扩容,所以对队列长度也是没有限制的。在具体情况下,适时选择。

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

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

相关文章

IP地址分类

IP地址是TCP/IP协议中非常关键的要素之一&#xff0c;它用于标识网络中的设备和主机。根据使用版本的不同&#xff0c;IP地址分为IPv4和IPv6两种类型。 IPv4&#xff08;Internet Protocol version 4&#xff09;是IP地址的第四个版本&#xff0c;采用32位二进制数来表示IP地址…

JavaWeb之EL表达式和JSTL标签库

文章目录 EL表达式基本介绍EL表达式搜索域数据的顺序EL表达式输出Bean的普通属性&#xff0c;数组属性&#xff0c;List集合属性&#xff0c;Map集合属性EL表达式 - 运算关系运算逻辑运算算数运算empty运算三元运算. 点运算 和 [] 中括号运算符 EL表达式的11个隐含对象EL获取四…

Java中Thread 类的五种基本用法(简介)

目录 一.线程创建 Lambda创建一个线程 基础格式 举例 运行结果 二.线程中断 第一种:设置变量方法 举例 运行结果 第二种:interrupted&#xff08;&#xff09;方法 举例 运行结果 三.线程等待 举例 运行结果 四.线程休眠 举例 五.获取线程实例 举例 运行结果 …

如何用梯度下降法求解数学建模的拟合问题——以logistics增长问题为例

引言 众所周知的是&#xff0c;在大学课程中一般只会教授一种拟合方法(也即参数估计方法)——最小二乘法。这是一种直接求解的方法&#xff0c;非常的有效&#xff0c;不仅是损失最小解&#xff0c;而且是最大似然解。只不过&#xff0c;有一个缺点&#xff0c;它只能解决线性…

Jenkins配置仅合并代码后触发流水线

使用GitLabJenkins集成&#xff0c; 使用Jenkins的Generic WebHook插件&#xff1b;此插件可以作为各个工具间集成使用的通用方式&#xff0c;但是遇到些场景需要写些代码。关于 “合并代码后触发Pipeline”的配置方式&#xff0c; 其实思路简单&#xff0c;实现和让我描述起来…

电脑怎样连接打印机?分享4个简单操作!

为了更方便学习&#xff0c;我买了一个打印机来打印需要用的资料&#xff0c;但是操作了半天还是没连接上&#xff0c;想请问一下有经验的朋友是怎么将打印机与电脑进行连接的呢&#xff1f; 在现代人的工作和生活中&#xff0c;打印机是一个重要的设备。我们可以利用打印机进行…

一文搞懂String、StringBuffer、StringBuilder三者的对比以及扩容机制

String:不可变的字符序列&#xff1b;底层使用char[]存储StringBuffer:可变的字符序列&#xff1b;线程安全的&#xff0c;效率低&#xff1b;底层使用char[]存储StringBuilder:可变的字符序列&#xff1b;jdk5.0新增的&#xff0c;线程不安全的&#xff0c;效率高&#xff1b;…

行为型设计模式09-中介者模式

&#x1f9d1;‍&#x1f4bb;作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 中介者模式 1、中介者模式介绍 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为设计模…

【MySQL数据库】MySQL 高级SQL 语句一

[TOC](MySQL 高级SQL 语句 一、MySQL 高级SQL 语句1.1select -显示表格中一个或数个字段的所有数据记录1.2distinct不显示重复的数据记录1.3where有条件查询1.4and、or且 或1.5in 显示已知的值的数据记录1.6between 显示两个值范围内的数据记录1.7通配符&#xff0c;通常通配符…

都2023年了,JavaScript ES6后的新(lao)特性,你用起来了吗?

前言 JavaScript ES6 指的是 ECMAScript 6&#xff0c;它是 JavaScript 语言第六版的规范。ES6 包含了很多新特性和语法糖&#xff0c;涵盖了从 ES6 开始至今所增加的所有特性。 因此&#xff0c;ES6 新特性是指从 ES6 开始新增到当前时刻所有的新特性&#xff0c;包括但不限…

FPGA XDMA 中断模式实现 PCIE X8 HDMI视频采集 提供工程源码和QT上位机源码

目录 1、前言2、我已有的PCIE方案3、PCIE理论4、总体设计思路和方案视频采集和缓存XDMA简介XDMA中断模式QT上位机及其源码 5、vivado工程详解6、上板调试验证7、福利&#xff1a;工程代码的获取 1、前言 PCIE&#xff08;PCI Express&#xff09;采用了目前业内流行的点对点串…

四肽-21——改善皮肤紧实感、光滑感和弹性

简介 四肽-21是一种来源于皮肤自身的四胜肽&#xff0c;它结构独特、能高效的促进细胞外基质合成&#xff0c;从而减少各种皱纹和改善皮肤衰老现象。与市场上非常受欢迎的基肽(Matrixyl&#xff09;相比&#xff0c;四肽-21效果更为突出。 Tetrapeptide-21 is a type of tetra…

LeetCode - #85 最大矩形(Top 100)

文章目录 前言1. 描述2. 示例3. 答案题解 1题解 2 关于我们 前言 本题为 LeetCode 前 100 高频题 本题由于没有合适答案为以往遗留问题&#xff0c;最近有时间将以往遗留问题一一完善。 我们社区陆续会将顾毅&#xff08;Netflix 增长黑客&#xff0c;《iOS 面试之道》作者&am…

解密后无法加载到指定模版,且模版名为空

问题如图&#xff1a; 原因&#xff1a;因为改变了项目的集成管理&#xff0c;导致变量丢失

Redis原理 - 五种数据类型的底层结构关系

原文首更地址&#xff0c;阅读效果更佳&#xff01; Redis原理 - 五种数据类型的底层结构关系 | CoderMast编程桅杆https://www.codermast.com/database/redis/base-datatype-implement.html #字符串对象String String 是 Redis 中最常见的数据存储类型。 其基本编码方式是 …

安卓蓝牙SDP协议数据包

1. SDP概念 我们想一想&#xff0c;两个陌生的设备&#xff08;之前未有过交互&#xff09;如何去发现对方支持什么服务呢&#xff1f;比如Host端的Controller怎么知道远程蓝牙设备是蓝牙耳机还是HID遥控器呢&#xff1f;我们需要一种协议&#xff0c;这种协议在蓝牙设备配对成…

Git-Desktop【使用说明】

仓库操作 简单的创建仓库、删除仓库 删除点击 Remove 即可 文件操作 1、提交文件到本地仓库 2、修改文件 Git没有修改文件这一说&#xff0c;它只会再次提交一个新的版本到仓库中&#xff0c;提交修改后的文件其实是在仓库创建了一个新的文件&#xff0c;只不过是一个不同的…

Optano.Modeling 简单教程

前言 在工作中遇到两个需求&#xff0c;将两个数学公式用 .NET 的数学库找到数学公式中某个未知数的最优解&#xff0c;我尝试了几个数学库都没有办法完美解决我的需求&#xff0c;直到找到 Optano.Modeling Optano.Modeling 官网&#xff1a;Optano.Modeling 官网 Optano.Mo…

AI操作视频的工具最新最强集合

AI的进化日新月异&#xff0c;很多之前只是在想象中的操作&#xff0c;已经有很多可以使用AI来完成了。最新的Stable Diffusion,ControlNet,EBsynth有哪些神奇的应用&#xff0c;如何一键替换视频中人物和场景&#xff0c;如何根据文字描述即可生成梦幻视频&#xff1f;我们整理…

DFS/回溯/动态规划算法的融会贯通

学算法认准 labuladong 后台回复课程查看精品课 点击卡片可搜索文章&#x1f447; 在线学习网站&#xff1a; https://labuladong.gitee.io/algo/ 经常有读者后台问我&#xff0c;DFS算法/回溯算法/动态规划算法之间的区别和联系是什么&#xff1f; 对于这个问题&#xff0c;我…