Java集合之LinkedList

 LinkedList底层实现就是(双向)链表,包括链表节点的插入、删除、修改等一系列操作。与数组实现的ArrayList相比,在链表中查找的效率要低很多,但是向表中插入、删除等操作效率就要高得多了。LinkedList在实现了List接口的同时,又实现了Queue、Deque接口,也就是说LinkedList还能够实现(双端)队列(FIFO)、栈(LIFO)的功能。




底层数据结构:


transient int size = 0; // 链表当前存储元素个数
transient Node<E> first; // 链表头节点
transient Node<E> last; // 链表尾节点

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;
    }
}


构造方法:



public LinkedList() { // 构建一个空链表
}

public LinkedList(Collection<? extends E> c) { // 根据集合c中的元素构建初始链表
    this();
    addAll(c); // 将集合c转换成数组,然后循环数组元素一个一个插入到链表中
}



node(index):


 LinkedList能够通过下标找到某个节点,这里的下标其实是某个节点与头结点的相对位置,0就是头结点、1就是头结点的后直接在后继节点,size-1就是尾节点。在指定位置插入、删除和修改等操作都要使用根据位置查找节点的node()方法。



/**
 * 返回指定位置的节点
 * 调用本方法前,都应该检测index值得合法性
 */
Node<E> node(int index) {
    // 查找算法:如果要找的节点位置在链表的前半部分,则从链表头开始向后查找,如果在后半部分,则从
    // 链表尾部开始向前查找。这能有效提高查找的效率。 比如在一个很长的链表中查找倒数第二个节点,只
    // 需要查找两次
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}


link*方法:



private void linkFirst(E e) { // 插入头结点
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode; // 修改头结点
    if (f == null) // 如果插入前链表头结点为空(空链表),则尾节点也指向这个新节点
        last = newNode;
    else // 否则修改之前的头结点的前驱为新的头结点
        f.prev = newNode;
    size++;
    modCount++;
}

void linkLast(E e) { // 插入尾节点
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode; // 修改尾节点
    if (l == null) // 如果插入前链表尾结点为空(空链表),则头节点也指向这个新节点
        first = newNode;
    else // 否则修改之前的尾节点的后继结点为新的尾节点
        l.next = newNode;
    size++;
    modCount++;
}

void linkBefore(E e, Node<E> succ) { // 在指定节点succ之前插入节点
    final Node<E> pred = succ.prev; // 保存指定节点的前驱节点pred
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode; // 修改succ节点的前驱为插入节点
    if (pred == null) // pred为null,说明succ是头结点,插入节点为新的头结点
        first = newNode;
    else // 否则,修改pred后继结点为新插入的节点
        pred.next = newNode;
    size++;
    modCount++;
}


unlink方法*:

private E unlinkFirst(Node<E> f) { // 删除头结点,返回删除节点中保存的元素
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null;
    first = next; // 修改头结点
    if (next == null) // 如果新的头结点为null,则修改后链表为空,令尾节点也也为null(之前与头结点相同)
        last = null;
    else // 修改新的头结点前驱为null
        next.prev = null;
    size--;
    modCount++;
    return element;
}

private E unlinkLast(Node<E> l) { // 删除尾结点,返回删除节点中保存的元素
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null;
    last = prev; // 修改尾节点
    if (prev == null) // 如果新的尾结点为null,则修改后链表为空,令头节点也为null(之前与尾节结点相同)
        first = null;
    else // 修改新的尾结点后继为null
        prev.next = null;
    size--;
    modCount++;
    return element;
}

E unlink(Node<E> x) { // 删除指定节点,并返回删除节点中保存的元素
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) { // 如果删除的是头结点,则头结点指向x后继节点
        first = next;
    } else { // 将节点x的前驱结点的后继节点指向x的后继结点
        prev.next = next;
        x.prev = null;
    }

    if (next == null) { // 如果删除的是尾节点,则尾节点指向x前驱节点
        last = prev;
    } else { // 将节点x的后继结点的前驱节点指向x的前驱节点
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

LinkedList栈操作:


 LInkedList实现了Deque接口,其中包括push、和pop两个方法。push操作是向链表插入新的头结点,pop操作是删除头结点并返回被删除节点中保存的元素。只对链表进行push和pop操作就能实现LIFO(后进先出)。


public void push(E e) { // 压栈操作
    addFirst(e); // addFirst中调用linkFirst
}

public E pop() { // 出栈操作,如果当前栈空,则报NoSuchElementException异常
    return removeFirst(); // removeFirst中调用unlinkFirst
}

LinkedList(双端)队列操作:


 Deque继承了Queue接口,在队列的基础上,实现了双端队列,于是LinkedList也可以当作双端队列来使用。队列操作包含一些列操作方法,这些队列方法实现了可以从任意一端进行插入和删除,也包括只返回不删除操作。根据需要选择不同的方法组合来达到编程目的。

// 第一类:特点:如果队头节点为null(队列空),报NoSuchElementException异常
public E element() {
    return getFirst();
}
public E remove() {
    return removeFirst();
}

// 第二类:peek,特点:只返队头(尾)元素不删除
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}
public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}
public E peekLast() {
    final Node<E> l = last;
    return (l == null) ? null : l.item;
}

// 第三类:poll,特点:返回队头(尾)元素并删除
public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}
public E pollFirst() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}
public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);
}

// 第四类:offer,特点:向队列头(尾)插入节点
public boolean offer(E e) {
    return add(e);
}
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}
public boolean offerLast(E e) {
    addLast(e);
    return true;
}

import java.util.Deque;
import java.util.LinkedList;

public class LinkedListDemo {
    public static void main(String[] args) {
        Deque<Integer> deque = new LinkedList<>();
        deque.offer(1);
        deque.offer(2);
        deque.offer(3);
        // deque: head{1, 2, 3}tail
        System.out.println(deque.poll()); // 1
        System.out.println(deque.poll()); // 2
        System.out.println(deque.poll()); // 3
        System.out.println(deque.poll()); // null

        Deque<Integer> stack = new LinkedList<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        // stack: top{3, 2, 1}bottom
        System.out.println(stack.pop()); // 3
        System.out.println(stack.pop()); // 2
        System.out.println(stack.pop()); // 1
    }
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值