当前位置: 首页 > article >正文

Java数据结构 (从0构建链表(LinkedList))

在本文中,我们将基于 MySingleLinkedList 类,深入探讨单链表的实现,包括创建、插入、删除等核心操作,同时分享完整的代码示例。单链表是一种灵活的数据结构,适用于处理需要频繁插入和删除操作的场景,例如实现队列 📜、链式栈 🔥、循环队列 🔄 等。在计算机科学和实际开发中,链表结构被广泛应用。它不仅可以高效地处理插入和删除操作,还能够根据实际需求实现不同的链表结构。🚀本篇不仅涵盖了基本功能的代码展示,还补充了各种操作的实现细节和优化建议,旨在让读者能够全面掌握单链表的使用与实现。😊💡

目录

链表的基本结构 📊

代码实现 🖥️

讲解 🧑‍🏫

a. 链表的基本操作 🔧

1. 链表的创建 🛠️

动态创建链表 💻

讲解 💡

2. 链表的遍历 🔍

讲解 📚

3. 获取链表长度 🔢

讲解 🧐

b. 插入操作 ➕

1. 头插法 🏁

讲解 ⚡

2. 尾插法 🔚

讲解 📈

3. 任意位置插入 🔄

讲解 📘

c. 删除操作 ❌

1. 删除第一个匹配的节点

讲解 🧠

2. 删除所有匹配的节点

讲解 🔍

3. 清空链表

讲解 💬

总结 🌟



链表的基本结构 📊

单链表由节点(ListNode)组成,每个节点包含三个部分:

  • 值域(val):存储节点的值。💎
  • 指针域(next):指向下一个节点。👉
  • 前驱指针(prev):尽管在单链表中未实际使用,它可以支持双向链表的扩展。🔄

代码实现 🖥️

以下是链表的基础结构代码:

public class MySingleLinkedList {
    static class ListNode {
        public ListNode next;
        public int val;
        public ListNode prev;

        public ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode head; // 头节点 🧑‍💻
    public ListNode last; // 尾节点 🎯
}

讲解 🧑‍🏫

  • 每个节点由 ListNode 类表示,包含数据域 val 和两个指针域 nextprev
  • head 用于标记链表的起始位置,last 标记链表的尾节点(可选)。
  • 相较于数组,链表在插入和删除操作中更具灵活性,但随机访问性能较差。链表的插入和删除操作无需移动其他元素,且通过指针的变动即可实现动态扩展,极大提高了操作效率。

链表的基本操作 🔧

1. 链表的创建 🛠️

手动链接节点的方式可以快速构建一个简单的链表。我们通过手动指定每个节点的连接方式,构造一个简单的链表。它适用于学习和理解链表基本结构:

public void createList() {
    ListNode listNode1 = new ListNode(1);
    ListNode listNode2 = new ListNode(2);
    ListNode listNode3 = new ListNode(3);
    ListNode listNode4 = new ListNode(4);
    ListNode listNode5 = new ListNode(5);

    this.head = listNode1;
    listNode1.next = listNode2;
    listNode2.next = listNode3;
    listNode3.next = listNode4;
    listNode4.next = listNode5;
    listNode5.next = null;
}
动态创建链表 💻

动态创建链表可以根据输入动态生成节点并构建链表,这样不仅增加了代码的灵活性,而且便于应对不同长度和结构的链表需求:

public void createListDynamic(int[] values) {
    if (values == null || values.length == 0) {
        return;
    }
    this.head = new ListNode(values[0]);
    ListNode cur = this.head;
    for (int i = 1; i < values.length; i++) {
        cur.next = new ListNode(values[i]);
        cur = cur.next;
    }
}

讲解 💡

  • 动态连接节点node1.next = node2 是构建链表的核心逻辑,通过该步骤,node1next 指向了 node2,形成了节点之间的连接,链表的扩展就是通过这种方式完成的。🎯这种方式也适用于链表的动态增长,在需要时可以随时添加新的节点。
  • 灵活性:动态方法使得链表的创建不再依赖固定的结构,可以根据用户的输入或其他需求动态生成节点,极大提高了代码的通用性。

2. 链表的遍历 🔍

通过遍历链表打印节点值,同时考虑链表为空时的情况,避免出现空指针异常。遍历链表时,需要从头节点开始依次访问每个节点:

public void display() {
    if (head == null) {
        System.out.println("链表为空");
        return;
    }
    ListNode cur = head;
    while (cur != null) {
        System.out.print(cur.val + " ");
        cur = cur.next;
    }
    System.out.println();
}

讲解 📚

  • 动态连接节点node1.next = node2 是构建链表、插入节点的基础,解决了链表动态扩展问题。
  • 安全遍历链表while (cur != null) 确保链表遍历过程可靠且高效,避免了指针错误或无限循环的问题。这样可以避免遍历时访问空指针或进入无限循环。⚠️
  • 前进推进的流畅性cur = cur.next 是链表遍历的核心语句,它使得 cur 从当前节点指向下一个节点,实现了链表的逐步推进。这个简洁的推进逻辑符合链表的递归结构特性,也使得代码更易于理解。😊

3. 获取链表长度 🔢

链表的长度可以通过遍历链表的所有节点来计算,直到达到链表的末尾:

public int size() {
    ListNode cur = head;
    int length = 0;
    while (cur != null) {
        length++;
        cur = cur.next;
    }
    return length;
}

讲解 🧐

  • 递增计数:每遍历一个节点,计数器 length 加 1,直至链表末尾,遍历过程确保了准确的节点统计。📊
  • 优化建议:可以增加一个 size 属性,在链表增删时动态更新,避免每次都需要遍历整个链表来计算长度。

插入操作

1. 头插法 🏁

public void addFirst(int val) {
    ListNode listNode = new ListNode(val);
    listNode.next = head;
    head = listNode;
}

讲解

  • 直接插入头部:将新节点的 next 指向当前头节点,并更新 head 指针,使新节点成为链表的头节点。这种方式的时间复杂度为 O(1),是最为高效的插入方法,尤其适用于栈结构等需要优先级插入的场景。🔝

2. 尾插法 🔚

public void addLast(int val) {
    ListNode listNode = new ListNode(val);
    if (head == null) {
        head = listNode;
        return;
    }
    ListNode cur = head;
    while (cur.next != null) {
        cur = cur.next;
    }
    cur.next = listNode;
}

讲解 📈

  • 遍历尾部:通过遍历找到尾节点,将其 next 指向新节点。尾插法的时间复杂度是 O(n),如果链表较长,效率较低。⏳
  • 优化建议:通过维护尾指针 last,可以直接将新节点插入尾部,避免每次遍历链表,提升效率。⚙️

3. 任意位置插入 🔄

public void addIndex(int index, int val) {
    if (index < 0 || index > this.size()) {
        System.out.println("索引不合法");
        return;
    } else if (index == 0) {
        this.addFirst(val);
        return;
    } else if (index == this.size()) {
        this.addLast(val);
        return;
    } else {
        ListNode cur = findIndexSubOne(index);
        ListNode node = new ListNode(val);
        node.next = cur.next;
        cur.next = node;
    }
}

private ListNode findIndexSubOne(int index) {
    int count = 0;
    ListNode cur = head;
    while (count != index - 1) {
        cur = cur.next;
        count++;
    }
    return cur;
}

讲解 📘

  • 定位前驱节点:通过 findIndexSubOne 方法找到目标索引的前驱节点,便于在目标位置进行插入。
  • 边界处理:针对索引为 0 或链表尾部的情况,分别调用头插法或尾插法。对其他位置的插入需要找到前驱节点,并调整指针,使得新节点被插入到正确的位置。🎯

删除操作

1. 删除第一个匹配的节点

public void remove(int val) {
    if (head == null) return;

    if (head.val == val) {
        head = head.next;
        return;
    }

    ListNode cur = head;
    while (cur.next != null) {
        if (cur.next.val == val) {
            cur.next = cur.next.next;
            return;
        }
        cur = cur.next;
    }
}

讲解 🧠

  • 遍历匹配:从头开始遍历链表,查找目标值所在节点。找到匹配节点后,通过调整前后节点的 next 指针,实现删除。
  • 特殊情况:若目标值位于头节点,则直接更新 head 指针,避免访问无效的 null 指针。⚠️

2. 删除所有匹配的节点

public void removeAllKey(int key) {
    ListNode cur = head;
    while (cur != null) {
        if (cur.val == key) {
            if (cur == head) {
                head = head.next;
            } else {
                cur.prev.next = cur.next;
                if (cur.next != null) {
                    cur.next.prev = cur.prev;
                }
            }
        }
        cur = cur.next;
    }
}

讲解 🔍

  • 批量删除:遍历链表并删除所有匹配的节点。
  • 双向链表的优势:通过 prev 指针更方便地更新节点关系,实现更高效的删除操作。⚡

3. 清空链表

public void clear() {
    ListNode cur = head;
    while (cur != null) {
        ListNode next = cur.next;
        cur.next = null;
        cur.prev = null;
        cur = next;
    }
    head = last = null;
}

讲解 💬

  • 逐一断开连接:通过遍历依次清除每个节点的引用,确保没有悬挂引用。
  • 释放内存:同时将头尾指针设为 null,彻底清空链表。🚀

总结 🌟

通过本文的讲解,我们实现了以下操作:

  1. 链表的创建和遍历。
  2. 插入操作:头插、尾插和任意位置插入。
  3. 删除操作:删除单个节点、删除所有匹配节点,以及清空链表。

链表作为基础数据结构,适用于动态插入和删除频繁的场景,但其随机访问性能较差。由于链表无法通过索引直接访问特定位置的节点,需要从头节点开始逐一遍历,因此在随机访问频繁的场景中可能不如数组高效。在实际开发中,链表的灵活性可以解决许多复杂的数据管理问题。希望这篇文章能帮助您更好地理解单链表的实现,也为后续学习更复杂的链表结构(如双向链表和循环链表)打下基础!😊🎉


http://www.kler.cn/a/515017.html

相关文章:

  • NIO | 什么是Java中的NIO —— 结合业务场景理解 NIO (二)
  • Kubernetes 集群中安装和配置 Kubernetes Dashboard
  • python学opencv|读取图像(四十)掩模:三通道图像的局部覆盖
  • Hadoop•搭建完全分布式集群
  • 数据结构-ArrayList和顺序表
  • Day 14 卡玛笔记
  • 自动扣webpack框架演示 | 某书 x-xray-traceid 签名算法分析记录
  • 低代码系统-氚云、宜搭控件对比
  • hive 自动检测、自动重启、记录检测日志、自动清理日志
  • 《Java核心技术 卷II》获取Web数据提交表单数据
  • PIC单片机HEX文件格式分析
  • 人工智能学习(三)之机器学习基本概念
  • Oracle 深入学习 Part 13: Maintaining Data Integrity(数据完整性维护)
  • 如何使用 findIndex() 方法查找数组中的第一个匹配元素的索引?
  • 国产编辑器EverEdit - 快捷目录
  • 盲道人行道分割YOLOV8SEG
  • 期刊论文左下角添加通讯作者和横线的方法
  • GeoJSON 数据
  • 人源化抗体的改造方式及其优势【卡梅德生物】
  • 可以免费使用的电子杂志制作平台
  • OFD、PDF 电子签章系统处理流程
  • 大模型GUI系列论文阅读 DAY3:《GPT-4V(ision) is a Generalist Web Agent, if Grounded》
  • matlab绘图——彩色螺旋图
  • 数据结构——实验六·散列表
  • Android SystemUI——通知栏构建流程(十六)
  • GA-CNN-LSTM-Attention、CNN-LSTM-Attention、GA-CNN-LSTM、CNN-LSTM四模型多变量时序预测一键对比