前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【数据结构】线性表 ⑥ ( 双循环链表 | 双循环链表插入操作 | 双循环链表删除操作 | LinkedList 双循环链表源码分析 )

【数据结构】线性表 ⑥ ( 双循环链表 | 双循环链表插入操作 | 双循环链表删除操作 | LinkedList 双循环链表源码分析 )

作者头像
韩曙亮
发布2023-10-11 16:51:58
2340
发布2023-10-11 16:51:58
举报
文章被收录于专栏:韩曙亮的移动开发专栏

一、双循环链表插入操作处理

双循环链表 中 , 需要对 插入 / 删除 / 遍历 操作 进行特殊处理 , 因为需要调节 前驱指针 和 后继指针 两个指针 ;

如 : 双循环链表 中 , 如果要插入元素 , 将 c 节点 插入到 a 节点 和 b 节点 之间 ,

当前的状态是 a 的后继指针 指向 b , b 的前驱指针指向 a ;

如果要实现插入 c 元素 , 则需要

  • 将 a 的 后继指针 指向 c ,
  • 将 c 的 前驱指针 指向 a ,
  • 将 c 的 后继指针 指向 b ,
  • 将 b 的 前驱指针 指向 c ;

插入节点操作 需要执行四个步骤 :

  • ① 将 c 的 前驱指针 指向 a
  • ② 将 a 的 后继指针 指向 c
  • ③ 将 c 的 后继指针 指向 b
  • ④ 将 b 的 前驱指针 指向 c
在这里插入图片描述
在这里插入图片描述

二、双循环链表删除操作处理


下面的链表插入成功 , 顺序为 a , c , b ,

在这里插入图片描述
在这里插入图片描述

如果要删除双循环链表中的 c 元素 , 只需要将 a 元素的 后继指针 指向 b , 将 b 元素的 前驱指针 指向 a 即可 ;

c 元素没有指针指向后 , 会自动被内存回收 ;

三、LinkedList 双循环链表源码分析


LinkedList 源码地址 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java

1、链表节点

LinkedList 链表是一个 双循环链表 , 下面的 Node 类 , 就是双循环链表的 节点 ;

代码语言:javascript
复制
    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;
        }
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#1021

2、LinkedList 链表中收尾元素指针

在 LinkedList 双循环链表中 , 维护了 首元素节点指针 transient Node<E> first , 尾元素节点指针 transient Node<E> last , 分别指向 首尾元素 ;

代码语言:javascript
复制
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

3、链表插入操作

LinkedList 双循环链表 调用 add 方法 添加元素 , 在其中调用了 linkLast 函数 , 将元素插入到了队尾 ;

代码语言:javascript
复制
    /**
     * 将指定的元素追加到此列表的末尾。
     *
     * <p>这个方法等价于 {@link #addLast}.
     *
     * @param e 元素添加到此列表
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#354

在 linkLast 函数中 , 创建了新的节点 , 将数据设置到了新节点中 , 最后将新节点设置为 尾部节点 ;

注意 , 设置新的尾部节点时 ,

  • 首先 , 保存原来的尾部节点指针 ( 现在不保存 , 之后访问不到了 ) ;
  • 然后 , 将新的节点设置为 尾部节点 ;
  • 最后 , 将原来的 尾部节点 的后继指针 指向新插入的节点 ;
代码语言:javascript
复制
    /**
     * 链接作为最后一个元素。
     */
    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++;
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#147

4、链表向指定位置插入操作

调用 LinkedList 的 public void add(int index, E element) 函数 , 可以向指定索引添加元素 ,

如果添加的非末尾元素 , 则调用 linkBefore 函数 向 链表 中插入数据 ;

代码语言:javascript
复制
    /**
     * 将指定元素插入此列表中的指定位置。
     * 将当前在该位置的元素(如果有的话)
     * 和任何后续元素向右移动(在它们的索引上加1)。
     *
     * @param index 要插入指定元素的索引
     * @param element 要插入的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
    	// 检查索引合法性
        checkPositionIndex(index);

        if (index == size)
        	// 如果是添加到末尾
            linkLast(element);
        else
        	// 如果是添加到非末尾的元素
            linkBefore(element, node(index));
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#532

void linkBefore(E e, Node<E> succ) 函数 , 是将 E 数据对应的节点插入到 Node<E> succ 数据之前 ;

代码语言:javascript
复制
    /**
     * 在非空节点 succ 前插入元素 e
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        // 获取 succ 节点 前驱节点
        final Node<E> pred = succ.prev;
        // 新建要插入的节点 
        final Node<E> newNode = new Node<>(pred, e, succ);
        // 前驱节点 的 后继指针 指向 新节点
        succ.prev = newNode;
        if (pred == null)
        	// 如果插入的 是 首元素节点
            first = newNode;
        else
        	// 后继节点 的 前驱指针 指向新节点
            pred.next = newNode;
        size++;
        modCount++;
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#163

5、获取指定索引的元素

LinkedList 中 , 调用 public E get(int index) 函数 , 获取指定索引的元素 ;

checkElementIndex 函数的作用是 检查该索引是否合法 ;

node 函数就是获取 双循环链表 元素的方法 ;

代码语言:javascript
复制
    /**
     * 返回列表中指定位置的元素。
     *
     * @param index 要返回的元素的索引
     * @return 在此列表中指定位置的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#500

Node<E> node(int index) 函数的核心操作 , 就是执行 index - 1 次 循环 , 找到对应的节点并返回 ;

在执行前 判定 index 靠近 首元素 还是 尾部元素 , 如果 index < (size >> 1) 可以判定为 index 处于前半部分 ;

  • 如果 index 靠近 首部元素 , 则正向遍历 ;
  • 如果 index 靠近 尾部元素 , 则逆向遍历 ;
代码语言:javascript
复制
    /**
     * 返回指定元素索引处的(非空)节点。
     */
    Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#596

6、删除指定索引的元素

LinkedList 双循环链表 中 , 调用 public E remove(int index) 函数 , 删除指定索引的元素 ;

删除的核心操作 , 就是 unlink 函数 , 将指定节点从 双循环链表 中脱离 ;

代码语言:javascript
复制
    /**
     * 移除此列表中指定位置的元素。
     * 将所有后续元素向左移动(从它们的索引中减去1)。
     * 返回从列表中删除的元素。
     *
     * @param index 要删除的索引
     * @return the 元素先前在指定位置
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#551

unlink 函数中 , 先获取要删除节点的 前驱节点 和 后继节点 , 然后 执行下面两个操作 :

  • 前驱节点 的 后继指针 指向 后继节点 ;
  • 后继节点 的 前驱指针 指向 前驱节点 ;
代码语言:javascript
复制
    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        // 获取节点的 前驱节点
        final Node<E> next = x.next;
        // 获取节点的 后继节点 
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
        	// 前驱节点 的 后继指针 指向 后继节点
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
        	// 后继节点 的 前驱指针 指向 前驱节点 
            next.prev = prev;
            x.next = null;
        }

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

源码参考 : https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/LinkedList.java#220

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、双循环链表插入操作处理
  • 二、双循环链表删除操作处理
  • 三、LinkedList 双循环链表源码分析
    • 1、链表节点
      • 2、LinkedList 链表中收尾元素指针
        • 3、链表插入操作
          • 4、链表向指定位置插入操作
            • 5、获取指定索引的元素
              • 6、删除指定索引的元素
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档