本文介绍了链表的基本概念、实现与应用。首先对比了链表与顺序表的特性差异:链表插入删除O(1)但访问O(n),空间不连续;顺序表访问O(1)但插入删除O(n),空间连续。重点演示了单链表的Java实现,包括节点结构、头插尾插、任意位置插入、删除等核心操作,并给出力扣203题(删除指定节点)和206题(反转链表)的解题思路。随后介绍了双向链表的优势(双向遍历、O(1)头尾操作)及其实现,最后对比LinkedList的特性与遍历方式,强调根据实际场景选择合适的数据结构(频繁增删选链表,随机访问选顺序表)。
链表的引入
我们在学链表之前学过顺序表
顺序表在随机访问快捷 o(1)时间复杂度 而删除 修改 是o(n)复杂度
于是链表在物理上不连续 在空间连续 它的修改 删除 就是o(1) 不用整个移动
引出来了链表
链表划分
头 有头 没头
方向 有单向和双向
循环 循环 不循环
几个组合 等 具有不同特性
一般链表构成
节点 节点里有数据 和存储下一个位置的引用
由一个个节点连接成链表
从堆上申请出来的节点

链表的模拟实现
package Book3;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28931
* Date: 2026-07-15
* Time: 13:12
*/
// 假设shxiain是自定义接口,无实现暂时保留
public class lianbiao implements shxiain {
// 静态内部节点类
static class aa {
public int b;
public aa d;
public aa(int b) {
this.b = b;
}
}
// 链表头节点,定义在外部类,每条链表独立,去掉static
private aa head;
// 构造方法:传入头节点初始化链表
public void chuang() {
aa a1 = new aa(1);
aa a2 = new aa(2);
aa a3 = new aa(3);
aa a4 = new aa(4);
a1.d = a2;
a2.d = a3;
a3.d = a4;
head = a1;
}
// 正确头插法:头部新增节点
public void addFirst(int data) {
aa newNode = new aa(data);
// 新节点的后继 = 原来的头节点
newNode.d = this.head;
// 更新头节点为新节点
this.head = newNode;
}
// 打印链表(属于lianbiao,外部实例可直接调用)
public void display() {
aa cur = head;
while (cur != null) {
System.out.print(cur.b + " ");
cur = cur.d;
}
System.out.println();
}
//尾插法
public void addLast(int data) {
aa b = new aa(data);
aa cur = head;
while (cur.d != null) {
cur = cur.d;
}
cur.d = b;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
aa d = new aa(data);
aa e = head;
if (index == 0) {
d.d = head;
head = d;
return;
}
for (int i = 0; i < index - 1; i++) {
e = e.d;
}
d.d = e.d;
e.d = d;
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
aa cur = head;
while (cur != null) {
if (cur.b == key) {
return true;
}
cur = cur.d;
}
return false;
}
//删除第一次出现关键字为key的节点
public void remove(int key) {
aa cur = head;
while (head != null && head.b == key) {
head = head.d;
}
if (head == null) {
return;
}
while (cur.d != null) {
if (cur.d.b == key) {
cur.d = cur.d.d;
return;
}
cur = cur.d;
}
}
public int size() {
aa cur = head;
int a = 0;
while (cur != null) {
a++;
cur = cur.d;
}
return a;
}
//删除所有值为key的节点
public void removeAllKey(int key) {
aa cur = head;
while (head != null && head.b == key) {
head = head.d;
}
if (head == null) {
return;
}
while (cur.d != null) {
if (cur.d.b == key) {
cur.d = cur.d.d;
return;
}
cur = cur.d;
}
}
public void clear() {
head=null;
}
public static void main(String\[\] args) {
// 手动构建链表 1→2→3→4
// 创建链表对象,传入头节点a1
lianbiao list = new lianbiao();
list.chuang();
list.addLast(1);
list.remove(1);
System.out.println(list.size());
list.clear();
list.display();
}
}
题目
删除链表中等于给定值 val的所有节点。
203. 移除链表元素 - 力扣(LeetCode)
考虑头节点和尾节点 空节点
if(head==null){
return null;
}
if(head!=null&&head.val==val){
head=head.next;
}
ListNode cur=head;
while(cur!=null){
//避免访问空节点 导致空指针异常
if(cur.next.val==val){
cur.next=cur.next.next;
}else{
cur=cur.next;
}
}
return head;
反转一个单链表
206. 反转链表 - 力扣(LeetCode)
public ListNode reverseList(ListNode head) {
//判断是否为空
if(head==null){
return null;
}
将第一个节点的下一个地址为null设置为最后一个节点 用头插法
ListNode cur=head.next;
head.next=null;
while(cur!=null){
//记录下一个节点的地址
ListNode cn=cur.next;
//更新坐标和位置
cur.next=head;
head=cur;
cur=cn;
}
return head;
}
其中链表的题目大量运用快慢指针的做法
双向链表的模拟实现
public class MyLinkedList {
// 头插法
public void addFirst ( int data ){ }
// 尾插法
public void addLast ( int data ){}
// 任意位置插入 , 第一个数据节点为 0 号下标
public void addIndex ( int index , int data ){}
// 查找是否包含关键字 key 是否在单链表当中
public boolean contains ( int key ){}
// 删除第一次出现关键字为 key 的节点
public void remove ( int key ){}
// 删除所有值为 key 的节点
public void removeAllKey ( int key ){}
// 得到单链表的长度
public int size (){}
public void display (){}
public void clear (){}
package Book3;
import java.util.LinkedList;
import java.util.ListIterator;
public class shaungxianglianbiao {
public int val;
public shaungxianglianbiao p;
public shaungxianglianbiao s;
public shaungxianglianbiao head;
public shaungxianglianbiao last;
public shaungxianglianbiao(int val) {
this.val = val;
}
public int size() {
int a = 0;
for(shaungxianglianbiao cur = this.head; cur != null; cur = cur.s) {
++a;
}
return a;
}
public void display() {
for(shaungxianglianbiao cur = this.head; cur != null; cur = cur.s) {
System.out.println(cur.val);
}
}
public void clear() {
this.head = this.last = null;
}
public void addFirst(int data) {
shaungxianglianbiao datas = new shaungxianglianbiao(data);
if (this.head == null) {
this.last = this.head = datas;
} else {
this.head.p = datas;
datas.s = this.head;
this.head = datas;
}
}
public void addLast(int data) {
shaungxianglianbiao a = new shaungxianglianbiao(data);
if (this.last == null) {
this.head = a;
this.last = a;
} else {
this.last.s = a;
a.p = this.last;
this.last = a;
}
}
public void addIndex(int index, int data) {
if (index >= 0 && index <= this.size()) {
if (index == 0) {
this.addFirst(data);
} else if (index == this.size()) {
this.addLast(data);
} else {
shaungxianglianbiao cur = this.head;
shaungxianglianbiao a = new shaungxianglianbiao(data);
for(int i = 0; i < index; ++i) {
cur = cur.s;
}
a.p = cur;
cur.s.p = a;
a.s = cur.s;
cur.s = a;
}
}
}
public boolean contains(int key) {
for(shaungxianglianbiao cur = this.head; cur != null; cur = cur.s) {
if (cur.val == key) {
return true;
}
}
return false;
}
public void remove(int key) {
if (this.head != null) {
shaungxianglianbiao cur;
for(cur = this.head; cur != null && cur.val != key; cur = cur.s) {
}
if (cur != null) {
shaungxianglianbiao p = cur.p;
shaungxianglianbiao s = cur.s;
if (cur == this.head) {
this.head = s;
if (this.head != null) {
p = null;
}
}
if (s == null) {
this.last = p;
} else {
s.p = p;
p.s = s;
}
}
}
}
public void removeAllKey(int key) {
if (this.head != null) {
shaungxianglianbiao nextNode;
for(shaungxianglianbiao cur = this.head; cur != null; cur = nextNode) {
nextNode = cur.s;
if (cur.val == key) {
shaungxianglianbiao p = cur.p;
shaungxianglianbiao s = cur.s;
if (p == null) {
this.head = s;
} else {
p.s = s;
}
if (s == null) {
this.last = p;
} else {
s.p = p;
}
cur.p = null;
cur.s = null;
}
}
}
}
public static void main(String[] args) {
shaungxianglianbiao a = new shaungxianglianbiao(-1);
a.addFirst(1);
a.remove(1);
a.display();
LinkedList<Integer> e = new LinkedList();
e.add(1);
e.add(2);
e.add(3);
ListIterator<Integer> it = e.listIterator(e.size());
while(it.hasPrevious()) {
System.out.print(it.previous());
}
}
}
特点
- 可双向遍历:既能从头往后走,也能从尾往前走;单向链表只能正向。
- 删除节点优势:拿到目标节点即可直接删,不用遍历找前驱;单向链表必须遍历找前驱。
- 头尾指针加持:头插、尾插、头尾删除都是 O(1),不需要遍历。
- 空间开销更大:每个节点多存 1 个
prev前驱指针。
LinkedList
- LinkedList 实现了 List 接口
- LinkedList 的底层使用了双向链表
- LinkedList 没有实现 RandomAccess 接口,因此 LinkedList 不支持随机访问
- LinkedList 的任意位置插入和删除元素时效率比较高,时间复杂度为 O(1)
- LinkedList 比较适合任意位置插入的场景
有参构造和无参构造
LinkedList a=new LinkedList();
LinkedList<Character> a=new LinkedList<>(LinkedList相同的类);
遍历方式
// foreach 遍历
for ( int e : list ) {
System . out . print ( e + " " );
}
System . out . println ();
// 使用迭代器遍历 --- 正向遍历
ListIterator < Integer > it = list . listIterator ();
while ( it . hasNext ()){
System . out . print ( it . next () + " " );
}
System . out . println ();
// 使用反向迭代器 --- 反向遍历
ListIterator < Integer > rit = list . listIterator ( list . size ());
while ( rit . hasPrevious ()){
System . out . print ( rit . previous () + " " );
链表和顺序表的区别
链表的插入删除 时间复杂度o(1) 但是随机访问下标是o(N) 在空间上不连续 但是在逻辑上连续
顺序表的插入删除时间复杂度 是o(N) 但是随机访问下标是o(1) 在空间上连续
所以正确运用链表和顺序表才能更好的运用