【数据结构】单链表核心知识点梳理
一、基本定义与结构
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。科普中国-单链表
1. 节点结构
单链表由节点串联而成,每个节点包含两部分:
- 数据域:存储元素值(如整数、字符串等)
- 指针域:通过引用(Java中)指向后继节点
Java节点类定义:
public class ListNode {int val; // 数据域ListNode next; // 指针域(指向后继节点)// 构造方法public ListNode(int val) {this.val = val;this.next = null; // 初始后继节点为null}// get、set方法public int getVal() {return val;}public void setVal(int val) {this.val = val;}public ListNode getNext() {return next;}public void setNext(ListNode next) {this.next = next;}}
2. 头节点的作用
-
带头节点:头节点不存储实际数据,其
next指向首节点(第一个数据节点)。优势是统一插入/删除操作逻辑(无需特殊处理首节点),算法题中常用。// 带头节点的链表初始化 ListNode head = new ListNode(-1); // 头节点(值无实际意义) -
不带头节点:首节点即第一个数据节点,操作首节点时需单独处理(如插入首节点需修改头指针),易出错,实际应用中较少使用。
二、创建方式
1. 头插法
- 核心逻辑:新节点的
next先指向头节点的后继,再将头节点的next指向新节点。 - 特性:链表元素顺序与插入顺序相反(逆序),可直接用于链表逆置。

Java示例:
public static ListNode createByHeadInsert(int[] elements) {ListNode head = new ListNode(-1); // 头节点for (int val : elements) {ListNode newNode = new ListNode(val);newNode.setNext(head.getNext());head.setNext(newNode);}return head;}
数据流转过程如下:
-
初始化头节点 :创建一个值为 -1 的头节点(哨兵节点),此时链表为空,
head.getNext()为 null -
遍历数组元素 :依次处理传入的每个元素值
-
数据流转核心步骤 (以数组 [1,2,3] 为例):
-
插入第一个元素 1 :
- 创建新节点 newNode(1)
- newNode.setNext(head.getNext()) → newNode.next = null
- head.setNext(newNode) → head.next = newNode(1)
- 链表状态:head(-1) -> 1 -> null
-
插入第二个元素 2 :
- 创建新节点 newNode(2)
- newNode.setNext(head.getNext()) → newNode.next = 节点1
- head.setNext(newNode) → head.next = newNode(2)
- 链表状态:head(-1) -> 2 -> 1 -> null
-
插入第三个元素 3 :
- 创建新节点 newNode(3)
- newNode.setNext(head.getNext()) → newNode.next = 节点2
- head.setNext(newNode) → head.next = newNode(3)
- 链表状态:head(-1) -> 3 -> 2 -> 1 -> null
2. 尾插法
- 核心逻辑:用尾指针
tail跟踪当前尾节点,每次将新节点连接到tail后,更新tail指向新节点。 - 特性:链表元素顺序与插入顺序一致(正序)。

Java示例:
public static ListNode createByTailInsert(int[] elements) {ListNode head = new ListNode(-1); // 头节点ListNode tail = head; // 尾指针初始指向头节点for (int val : elements) {ListNode newNode = new ListNode(val);tail.setNext(newNode);tail = newNode;}tail.setNext(null);return head;}
-
初始化阶段 :
- 创建一个值为 -1 的头节点(哨兵节点)
- 将尾指针 tail 初始指向头节点
- 此时链表为空, head.getNext() 和 tail.getNext() 都为 null
-
遍历数组元素 :依次处理传入的每个元素值
-
数据流转核心步骤 (以数组 [1,2,3] 为例):
-
插入第一个元素 1 :
- 创建新节点 newNode(1)
- tail.setNext(newNode) → 此时 tail 指向 head,所以 head.next = newNode(1)
- tail = newNode → 尾指针移动到新节点1
- 链表状态:head(-1) -> 1 -> null
-
插入第二个元素 2 :
- 创建新节点 newNode(2)
- tail.setNext(newNode) → 此时 tail 指向节点1,所以 1.next = newNode(2)
- tail = newNode → 尾指针移动到新节点2
- 链表状态:head(-1) -> 1 -> 2 -> null
-
插入第三个元素 3 :
- 创建新节点 newNode(3)
- tail.setNext(newNode) → 此时 tail 指向节点2,所以 2.next = newNode(3)
- tail = newNode → 尾指针移动到新节点3
- 链表状态:head(-1) -> 1 -> 2 -> 3 -> null
-
-
收尾处理 :
- 设置尾节点的 next 为 null: tail.setNext(null)
三、基本操作
1. 插入操作
- 场景:在节点
p后插入新节点newNode。 - 步骤(顺序不可颠倒):
newNode.next = p.next(新节点先连接p的后继)//newNode.setNext(p.getNext());p.next = newNode(p再指向新节点)//p.setNext(newNode);

Java示例:
代码中用的是get,set方法(请看标题:1. 节点结构 中的内容),代码中没用.next什么的,get,set看着更舒服(我感觉)
public static void insertAfter(ListNode p, int val) {if (p == null) return;ListNode newNode = new ListNode(val);newNode.setNext(p.getNext());p.setNext(newNode);}
2. 删除操作
- 场景:删除节点
p的后继节点。 - 步骤:
- 找到待删节点的前驱
p p.next = p.next.next(p直接指向待删节点的后继,跳过待删节点)- (Java中无需手动释放内存,由GC自动回收)
- 找到待删节点的前驱

【鉴于个人视角的有限性,所述观点未必全面,欢迎理性探讨与指正】
节点2指向节点3的引用(“2到3这条线”)暂时依然存在,但由于节点2不再被链表中的任何其他节点引用,它变成了一个孤立节点
当JVM的垃圾回收器检测到节点2不再被任何活动引用指向时,会将其标记为可回收
最终在垃圾回收过程中,节点2占用的内存会被释放
节点2内部的所有引用(包括它指向节点3的引用)也会一并消失
所以最后2到3这条线没了
Java示例:
代码中用的是getset方法(请看标题:1. 节点结构 中的内容)
public static void deleteAfter(ListNode p) {if (p == null || p.getNext() == null) return;p.setNext(p.getNext().getNext());}
本文内容仅供参考,不构成任何形式的专业建议。作者尽力确保信息的准确性,但不对因使用本文内容而引发的任何直接或间接损失负责。读者应自行核实信息并咨询相关专业人士。
