zoukankan      html  css  js  c++  java
  • Link

    LinkedList

    参考:https://www.cnblogs.com/leesf456/p/5308843.html

    一、LinkedList数据结构

    还是老规矩,先抓住LinkedList的核心部分:数据结构,其数据结构如下

      说明:如上图所示,LinkedList底层使用的双向链表结构,有一个头结点和一个尾结点,双向链表意味着我们可以从头开始正向遍历,或者是从尾开始逆向遍历,并且可以针对头部和尾部进行相应的操作

     二、LinkedList源码分析

       2.1、类的继承关系

    1 public class LinkedList<E>
    2     extends AbstractSequentialList<E>
    3     implements List<E>, Deque<E>, Cloneable, java.io.Serializable

       说明:LinkedList的类继承结构很有意思,我们着重要看是Deque接口,Deque接口表示是一个双端队列,那么也意味着LinkedList是双端队列的一种实现,所以,基于双端队列的操作在LinkedList中全部有效。

      2.2、类的内部类

     1 private static class Node<E> {
     2         E item; // 数据域
     3         Node<E> next; // 后继
     4         Node<E> prev; // 前驱
     5         
     6         // 构造函数,赋值前驱后继
     7         Node(Node<E> prev, E element, Node<E> next) {
     8             this.item = element;
     9             this.next = next;
    10             this.prev = prev;
    11         }
    12     }

      说明:内部类Node就是实际的结点,用于存放实际元素的地方。

      2.3、类的属性

    1 public LinkedList(Collection<? extends E> c) {
    2         // 调用无参构造函数
    3         this();
    4         // 添加集合中所有的元素
    5         addAll(c);
    6     }

      

      

    .  2.4 、类的构造函数

      1、 LinkedList()型构造函数

    1 public LinkedList() {
    2 }

      

      2、 LinkedList(Collection<? extends E>)型构造函数

    1 public LinkedList(Collection<? extends E> c) {
    2         // 调用无参构造函数
    3         this();
    4         // 添加集合中所有的元素
    5         addAll(c);
    6     }

      

      2.5、 核心函数分析

      1. add函数

    1  public boolean add(E e) {
    2         // 添加到末尾
    3         linkLast(e);
    4         return true;
    5     }

      说明:add函数用于向LinkedList中添加一个元素,并且添加到链表尾部。具体添加到尾部的逻辑是由linkLast函数完成的

     1 void linkLast(E e) {
     2         // 保存尾结点,l为final类型,不可更改
     3         final Node<E> l = last;
     4         // 新生成结点的前驱为l,后继为null
     5         final Node<E> newNode = new Node<>(l, e, null);
     6         // 重新赋值尾结点
     7         last = newNode;    
     8         if (l == null) // 尾结点为空
     9             first = newNode; // 赋值头结点
    10         else // 尾结点不为空
    11             l.next = newNode; // 尾结点的后继为新生成的结点
    12         // 大小加1    
    13         size++;
    14         // 结构性修改加1
    15         modCount++;
    16     }

     说明:对于添加一个元素至链表中会调用add方法 -> linkLast方法。

      对于添加元素的情况我们使用如下示例进行说明

      示例一代码如下(只展示了核心代码) 

    List<Integer> lists = new LinkedList<Integer>();
    lists.add(5);
    lists.add(6);

    说明:首先调用无参构造函数,之后添加元素5,之后再添加元素6。具体的示意图如下:

      说明:上图的表明了在执行每一条语句后,链表对应的状态。

      2. addAll函数

      addAll有两个重载函数,addAll(Collection<? extends E>)型和addAll(int, Collection<? extends E>)型,我们平时习惯调用的addAll(Collection<? extends E>)型会转化为addAll(int, Collection<? extends E>)型,所以我们着重分析此函数即可。

     1 // 添加一个集合
     2     public boolean addAll(int index, Collection<? extends E> c) {
     3         // 检查插入的的位置是否合法
     4         checkPositionIndex(index);
     5         // 将集合转化为数组
     6         Object[] a = c.toArray();
     7         // 保存集合大小
     8         int numNew = a.length;
     9         if (numNew == 0) // 集合为空,直接返回
    10             return false;
    11 
    12         Node<E> pred, succ; // 前驱,后继
    13         if (index == size) { // 如果插入位置为链表末尾,则后继为null,前驱为尾结点
    14             succ = null;
    15             pred = last;
    16         } else { // 插入位置为其他某个位置
    17             succ = node(index); // 寻找到该结点
    18             pred = succ.prev; // 保存该结点的前驱
    19         }
    20 
    21         for (Object o : a) { // 遍历数组
    22             @SuppressWarnings("unchecked") E e = (E) o; // 向下转型
    23             // 生成新结点
    24             Node<E> newNode = new Node<>(pred, e, null);
    25             if (pred == null) // 表示在第一个元素之前插入(索引为0的结点)
    26                 first = newNode;
    27             else
    28                 pred.next = newNode;
    29             pred = newNode;
    30         }
    31 
    32         if (succ == null) { // 表示在最后一个元素之后插入
    33             last = pred;
    34         } else {
    35             pred.next = succ;
    36             succ.prev = pred;
    37         }
    38         // 修改实际元素个数
    39         size += numNew;
    40         // 结构性修改加1
    41         modCount++;
    42         return true;
    43     }

      说明:参数中的index表示在索引下标为index的结点(实际上是第index + 1个结点)的前面插入。在addAll函数中,addAll函数中还会调用到node函数,get函数也会调用到node函数,此函数是根据索引下标找到该结点并返回,具体代码如下

     1 Node<E> node(int index) {
     2         // 判断插入的位置在链表前半段或者是后半段
     3         if (index < (size >> 1)) { // 插入位置在前半段
     4             Node<E> x = first; 
     5             for (int i = 0; i < index; i++) // 从头结点开始正向遍历
     6                 x = x.next;
     7             return x; // 返回该结点
     8         } else { // 插入位置在后半段
     9             Node<E> x = last; 
    10             for (int i = size - 1; i > index; i--) // 从尾结点开始反向遍历
    11                 x = x.prev;
    12             return x; // 返回该结点
    13         }
    14     }

      说明:在根据索引查找结点时,会有一个小优化,结点在前半段则从头开始遍历,在后半段则从尾开始遍历,这样就保证了只需要遍历最多一半结点就可以找到指定索引的结点。

      下面通过示例来更深入了解调用addAll函数后的链表状态。 

    List<Integer> lists = new LinkedList<Integer>();
    lists.add(5);
    lists.addAll(0, Arrays.asList( 3, 4, 5));

    上述代码内部的链表结构如下:

      

      3. unlink函数

      在调用remove移除结点时,会调用到unlink函数,unlink函数具体如下:

     1 E unlink(Node<E> x) {
     2         // 保存结点的元素
     3         final E element = x.item;
     4         // 保存x的后继
     5         final Node<E> next = x.next;
     6         // 保存x的前驱
     7         final Node<E> prev = x.prev;
     8         
     9         if (prev == null) { // 前驱为空,表示删除的结点为头结点
    10             first = next; // 重新赋值头结点
    11         } else { // 删除的结点不为头结点
    12             prev.next = next; // 赋值前驱结点的后继
    13             x.prev = null; // 结点的前驱为空,切断结点的前驱指针
    14         }
    15 
    16         if (next == null) { // 后继为空,表示删除的结点为尾结点
    17             last = prev; // 重新赋值尾结点
    18         } else { // 删除的结点不为尾结点
    19             next.prev = prev; // 赋值后继结点的前驱
    20             x.next = null; // 结点的后继为空,切断结点的后继指针
    21         }
    22 
    23         x.item = null; // 结点元素赋值为空
    24         // 减少元素实际个数
    25         size--; 
    26         // 结构性修改加1
    27         modCount++;
    28         // 返回结点的旧元素
    29         return element;
    30     }

      说明:将指定的结点从链表中断开,不再累赘

  • 相关阅读:
    我爱Java系列之---【SpringBoot打成war包部署】
    279. Perfect Squares
    矩阵dfs--走回路
    112. Path Sum
    542. 01 Matrix
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    Invert Binary Tree
    563 Binary Tree Tilt
    145 Binary Tree Postorder Traversal
  • 原文地址:https://www.cnblogs.com/xiaocao123/p/10542922.html
Copyright © 2011-2022 走看看