队列在Java类库中的链式表示及实现——LinkedList

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

队列是一种先进先出的数据结构。它主要包含两种操作:

(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,在实际应用中根据需求选择。

还可以看出,只要我们只在双端队列的一端(队头或者队尾)进行出队和入队操作,就可以把双端队列当作栈来使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值