首页 > 编程知识 正文

数组实现循环队列,循环队列是逻辑结构吗

时间:2023-05-06 10:06:07 阅读:17679 作者:3123

我们知道,数据结构(如队列)的物理实现方式主要有两种:使用链队列(自定义节点类)和数组实现。 这两者都有好处。 这里介绍的循环队列实际上是队列的一个具体实现,在一般的数组实现队列结构中,由于频繁出队时会发生泛洪现象,降低数组的使用效率,因此引入了循环队列结构。 本文从以下两个大角度讨论循环队列这一数据结构:

循环数组实现循环队列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主要具有以下属性域:

传输对象[ ] elements; 传输头; 传输失败; 私有staticfinalintmin _ initial _ capacity=8; elements介绍了存储队列中的每个节点,但ArrayDeque对数组的长度没有限制,并使用动态扩展机制动态扩展数组的容量。 头和尾分别表示头指针和尾指针。 MIN_INITIAL_CAPACITY表示创建队列的最小容量。 具体使用情况将在以下详细说明。 让我们来看看一些构造函数:

publicarraydeque ((元素=new object [ 16 ]; } publicarraydeque (内部元素) allocateelements (内部元素) numelements; 如果未指定明确传递给elements的长度,则默认值为16。 如果显式传递了表示元素长度的变量,则调用allocateElements进行简单处理,以获取最接近numElements的2的指数值,而不仅仅是将传递的参数用于生成元素。 例如,如果numElements为20,则如果传递的参数小于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的主要优势在于尾部添加元素,头部出队元素的效率是比较高的,内部使用位操作来判断队满条件,效率相对有所提高,并且该结构使用动态扩容,所以对队列长度也是没有限制的。在具体情况下,适时选择。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。