首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【重学数据结构】链表 LinkedList

【重学数据结构】链表 LinkedList

作者头像
程序员三明治
发布2025-12-18 20:02:04
发布2025-12-18 20:02:04
1110
举报
文章被收录于专栏:码力up码力up

链表是什么?

链表是数据元素的线性集合,元素的线性顺序并不对应于内存的物理地址顺序,每个元素指向下一个元素,这样构成了线性序列。

链表的分类

链表有单向链表、双向链表两种类型。

单向链表

一开始我以为ArrayList是单向链表,虽然说ArrayList也实现了List接口,后面仔细查了查发现并不是,单向链表是一个节点只有后向指针的链表,数据结构如图。

代码上就是LinkedList中的Node节点没有pre指针,只有next指针,另外list的属性也是只有head和size,没有tail只想尾部的指针。

双向链表

双向链表中的Node节点既有next指针也有pre指针,另外list的属性也是有size、head和tail,数据结构如下图。

双向链表的实现 

平常我们多使用LinkedList双向链表,这里手写一个双向链表来举例。

链表内部类节点

因为链表所有的核心操作都是要对节点的使用,所以节点类是链表的关键。

代码语言:javascript
复制
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E item, Node<E> next) {
        this.item = item;
        this.prev = prev;
        this.next = next;
    }
}

头插节点 

代码语言:javascript
复制
    void linkFirst(E e) {
        // 保存当前头部节点
        Node<E> first = head;
        // 创建新节点,插入为新的头部节点
        Node<E> newNode = new Node<E>(null, e, first);
        // 更新头部节点为新节点
        head = newNode;
        // 如果链表之前为空(即原头节点为null),则新节点也是尾部节点
        if (first == null) {
            tail = newNode;
        } else {
            // 否则,更新原头部节点的前驱指针,指向新节点
            first.prev = newNode;
        }
        // 增加链表大小计数
        size++;
    }

尾插节点

代码语言:javascript
复制
    void linkLast(E e) {
        // 保存当前尾部节点
        Node<E> last = tail;
        // 创建新节点,它的前驱是当前的尾部节点,后继为null
        Node<E> newNode = new Node<>(last, e, null);
        // 更新尾节点为新节点
        tail = newNode;
        // 如果尾节点为空,说明列表之前是空的,新节点也应该是头部节点
        if (last == null) {
            head = newNode;
        } else {
            // 否则,更新原尾节点的后继引用为新节点
            last.next = newNode;
        }
        size++;
    }

 拆链操作

代码语言:javascript
复制
   void unlink(Node<E> x) {
        Node<E> next = x.next;
        Node<E> prev = x.prev;

        if (prev == null) {
            head = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            tail = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }
        x.item = null;
        size--;
    }

删除操作

删除一个元素必须要先for循环找到此元素,然后对其进行拆链。循环的过程是一个 O(n) 的操作,删除(拆链)的过程是一个 O(1) 的操作。

代码语言:javascript
复制
    @Override
    public boolean remove(E e) {
        if (e == null) {
            for (Node<E> x = head; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = head; x != null; x = x.next) {
                if (x.item.equals(e)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

 链表功能自测

代码语言:javascript
复制
public static void main(String[] args) {
        List<String> list = new LinkedList<>();
        // 添加元素
        list.add("a");
        list.addFirst("b");
        list.addLast("c");
        // 打印列表
        list.printLinkedList();
        // 头插元素
        list.addFirst("d");
        // 删除元素
        list.remove("b");
        // 打印列表
        list.printLinkedList();
    }

 测试结果

image.png
image.png

常见问题

描述一下链表的数据结构?

链表是一个由一个个节点构成的数据结构,每一个节点都有前驱指针和后向指针,所有的节点构成了线性序列,但是线性顺序并不对应于内存上物理地址的顺序。

Java 中 LinkedList 使用的是单向链表、双向链表还是循环链表?

双向链表 

链表中数据的插入、删除、获取元素,时间复杂度是多少?

插入是O1 删除是O1,但是为了找到要删除的节点,需要for循环遍历,时间复杂度为On 获取元素是On 

什么场景下使用链表更合适?

插入删除操作较多,查询元素操作较低的场景下更合适。查询操作更推荐用ArrayList。 

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 链表是什么?
  • 链表的分类
    • 单向链表
    • 双向链表
  • 双向链表的实现 
    • 链表内部类节点
    • 头插节点 
    • 尾插节点
    •  拆链操作
    • 删除操作
  •  链表功能自测
  • 常见问题
    • 描述一下链表的数据结构?
    • Java 中 LinkedList 使用的是单向链表、双向链表还是循环链表?
    • 链表中数据的插入、删除、获取元素,时间复杂度是多少?
    • 什么场景下使用链表更合适?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档