队列是一种先进先出的数据结构。它主要包含两种操作:
(1)入队, 向队尾添加一个元素;
(2)出队,从队首删除一个元素。
常规队列只有一个队首和一个队尾;双端队列是队首同时是队尾,队尾同时是队首的队列,也就是说,可以从双端队列的两端删除元素,也可以向双端队列的两端添加元素。
在Java类库中,Queue接口列出了常规队列应该拥有的操作;Deque接口列出了双端队列应该拥有的操作,并继承了Queue接口;ArrayDeque类和LinkedList类实现了Deque接口,这里讲LinkedList类如何实现常规队列和双端队列的。
LinkedList用一个整数来存储元素的个数:
transient int size = 0;用静态内部类来表示元素:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}节点有指向前驱的指针,也有指向后继的指针。实际上,LinkedList是一个双向链表。这个双向链表还有分别指向头节点和尾节点的指针:
transient Node<E> first;transient Node<E> last;我们知道,队列是受限的列表,接下来就介绍如何定义列表上受限的操作来实现队列和双端队列。
1.队列
(1)向队尾添加一个元素
public boolean add(E e) {
linkLast(e);
return true;
}add(E e)调用
linkLast(e);向队尾添加一个元素,linkedLast(E e)的实现如下:
void linkLast(E e) {
// 让l指向链表之前的最后一个元素
final Node<E> l = last;
// 新元素的前驱是之前的最后一个元素,没有后继
final Node<E> newNode = new Node<>(l, e, null);
// 新元素成为新的最后一个元素
last = newNode;
/**
* l == null,说明之前链表为空,说明现在的最后一个元素也是现在的第一个元素;
* l != null,说明之前的链表不为空,说明现在的最后一个元素不是现在的第一个元素,也就是说,
* 之前的最后一个元素的后继是现在的最后一个元素。
*/
if (l == null)
first = newNode;
else
l.next = newNode;
// 链表的尺寸增1
size++;
modCount++;
}(2)从队头删除一个元素
public E remove() {
return removeFirst();
}remove()调用
removeFirst();移除队头元素,removeFirst()的实现如下:
public E removeFirst() {
final Node<E> f = first;
// f == null,说明链表为空
if (f == null)
throw new NoSuchElementException();
// 链表不为空,就删除链表第一个元素
return unlinkFirst(f);
}removeFirst()又调用unlinkFirst(Node<E> f)来移除队头元素,unlinkFirst(Node<E> f)的实现如下:
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
// 获得指向第二个元素的指针
final Node<E> next = f.next;
// 把first的保存的元素和指向下一个元素的指针都设置为null,使它们被回收
f.item = null;
f.next = null; // help GC
// 新的first是之前的第二个元素
first = next;
/**
* first == null,说明旧的链表第一个元素也是最后一个元素,现在链表为空,那么指向链表最后一个元素的指针也为null;
* first != null,说明现在链表不为空,链表第一个元素的前驱为null
*/
if (next == null)
last = null;
else
next.prev = null;
// 链表长度减1
size--;
modCount++;
return element;
}2.双端队列
(1)向队尾添加一个元素
public void addLast(E e) {
linkLast(e);
}addlast(E e)调用linkLast(e);向队尾添加一个元素,linkLast(E e)在上面已经说过了。
(2)从队尾删除一个元素
public E removeLast() {
final Node<E> l = last;
// l == null说明链表为空
if (l == null)
throw new NoSuchElementException();
// 如果链表不为空,就删除队尾元素
return unlinkLast(l);
}
removeLast()调用unlinkLast(Node<E> l)删除队尾元素,unlinkLast(Node<E> l)的实现如下:
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
// 获得指向倒数第二个元素的指针
final Node<E> prev = l.prev;
// 设置链表最后一个元素中保存的元素和指向前驱的指针为null,使它们被回收
l.item = null;
l.prev = null; // help GC
// 旧的最后一个元素的前驱成为新的最后一个元素
last = prev;
/**
* last == null,说明旧的链表的最后一个元素就是也是链表第一个元素,现在队列为空;
* last != null,说明现在链表不为空,链表最后一个元素的后继指针设为空
*/
if (prev == null)
first = null;
else
prev.next = null;
// 链表长度减1
size--;
modCount++;
return element;
}(3)向队头添加一个元素
public void addFirst(E e) {
linkFirst(e);
}addFirst(E e)调用linkFirst(e);向队头添加一个元素,linkFirst(E e)的实现如下:
private void linkFirst(E e) {
// 让f指向链表之前的第一个元素
final Node<E> f = first;
// 新元素的后继是之前的第一个元素,没有前驱元素
final Node<E> newNode = new Node<>(null, e, f);
// 新元素成为新的第一个元素
first = newNode;
/**
* f == null,说明之前链表为空,现在的第一个元素也是最后一个元素;
* f != null,说明之前的链表不为空,现在的第一个元素不是最后一个元素,
* 之前的第一个元素的前驱是现在的第一个元素。
*/
if (f == null)
last = newNode;
else
f.prev = newNode;
// 队列的长度增1
size++;
modCount++;
}(4)从队头删除一个元素
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}这个方法之前已经讲过了。除了上述操作,LinkedList还有其它一些队列的出队入队的操作,功能大同小异,比如从队列中删除元素,当队列为空时,remove()抛出异常,而poll()返回null,在实际应用中根据需求选择。
还可以看出,只要我们只在双端队列的一端(队头或者队尾)进行出队和入队操作,就可以把双端队列当作栈来使用。

本文介绍了Java中LinkedList类如何实现队列和双端队列的功能。通过具体的方法实现细节,展示了元素如何在队列的两端进行添加和删除操作。


被折叠的 条评论
为什么被折叠?



