黑马数据结构讲义
基础数据结构-031-单向链表-insert
链表任意位置插入元素教学文档
一、核心实现逻辑
- 插入过程示意图
原始链表:1 -> 2 -> 3 -> 4
插入位置:索引2(插入值7)
操作步骤:
1. 找到索引1的节点(值为2)
2. 创建新节点7
3. 新节点.next = 原索引2节点(值为3)
4. 索引1节点.next = 新节点7
结果链表:1 -> 2 -> 7 -> 3 -> 4
- 关键代码实现
public void insert(int index, E value) {
// 处理头节点插入特例
if (index == 0) {
addFirst(value);
return;
}
// 获取前驱节点
Node<E> previous = findNode(index - 1);
if (previous == null) {
throw new IndexOutOfBoundsException("非法索引: " + index);
}
// 创建新节点并调整指针
Node<E> newNode = new Node<>(value);
newNode.next = previous.next;
previous.next = newNode;
}
二、异常处理机制
- 非法索引检测
• 当previous == null
时抛出异常
• 支持的错误场景:
• 索引为负数
• 索引超过链表长度
• 非连续索引插入(如链表长度2时插入索引5)
- 专用异常方法
private void throwIllegalIndex(int index) {
throw new IndexOutOfBoundsException("非法索引: " + index);
}
三、边界情况处理
头节点插入特例
// 当index=0时直接调用已有方法
if (index == 0) {
addFirst(value);
return;
}
四、测试用例验证
- 常规插入测试
// 初始链表:1->2->3->4
list.insert(2, 5);
// 验证结果:1->2->5->3->4
- 尾部插入测试
// 初始链表:1->2->3->4
list.insert(4, 5);
// 验证结果:1->2->3->4->5
- 头部插入测试
// 初始链表:1->2->3->4
list.insert(0, 5);
// 验证结果:5->1->2->3->4
- 异常场景测试
try {
list.insert(5, 10); // 链表长度为4
} catch (IndexOutOfBoundsException e) {
System.out.println(e.getMessage()); // 输出"非法索引: 5"
}
五、核心方法说明
findNode方法
/**
* 根据索引获取对应节点
* @param index 目标索引(从0开始)
* @return 找到的节点对象,未找到返回null
*/
private Node<E> findNode(int index) {
// 实现细节略...
}
时间复杂度分析
操作类型 | 时间复杂度 |
---|---|
头部插入 | O(1) |
中间/尾部插入 | O(n) |
六、教学要点总结
- 前驱节点定位:必须准确获取index-1位置的节点
- 指针调整顺序:先设置新节点指针,再修改前驱节点指针
- 异常处理前置:在操作链表前完成索引合法性校验
- 边界条件处理:单独处理头节点插入场景
- 代码复用原则:复用已有方法(如addFirst)提升代码质量
七、扩展思考
- 如何实现链表插入操作的O(1)时间复杂度?
- 双向链表的插入与单向链表有何不同?
- 循环链表的插入操作需要注意哪些特殊场景?
基础数据结构-032-单向链表-removeFirst
链表节点删除操作教学文档
一、方法概述
本节讲解链表节点的两种删除方法:
- 删除第一个节点(remove_first)
- 按索引进行删除(待后续讲解)
二、删除第一个节点实现原理
链表结构示例
初始链表:head → 1 → 2 → 3 → 4 → None删除逻辑
• 操作目标:将头指针指向原第二个节点
• 实现方式:head = head.next
• 内存管理:
• Java:自动垃圾回收(无引用节点自动回收)
• C语言:需手动释放内存
- 特殊情况处理
• 空链表判断:当head为null时抛出异常
三、代码实现(Java)
public void removeFirst() {
if (head == null) {
throw new IllegalArgumentException("链表为空,无节点可删除");
}
head = head.next;
}
四、实现步骤详解
有效性校验
• 检查头指针是否为null• 空链表抛出IllegalArgumentException
节点删除
• 通过head.next
获取第二个节点• 将头指针指向第二个节点
内存管理说明
• 原第一个节点失去引用后被GC回收
五、测试案例
测试场景1:基础删除
// 初始链表:1 → 2 → 3 → 4 → None
removeFirst();
// 结果链表:2 → 3 → 4 → None
测试场景2:连续删除
removeFirst(); // 剩余:3 → 4 → None
removeFirst(); // 剩余:4 → None
removeFirst(); // 链表为空
测试场景3:空链表删除
try {
removeFirst();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // 输出:链表为空,无节点可删除
}
六、注意事项
空指针防护
• 必须进行head非空检查• 避免NullPointerException
语言特性差异
• C/C++需手动释放内存• Python使用引用计数机制
时间复杂度
• 删除操作时间复杂度:O(1)
七、可视化演示
初始状态:
[Head] → [1|→] → [2|→] → [3|→] → [4|→] → null
执行removeFirst()后:
[Head] → [2|→] → [3|→] → [4|→] → null
原节点1失去引用,等待GC回收。
八、扩展思考
- 如何实现删除最后一个节点?
- 当需要同时维护尾指针时,删除操作需要注意什么?
- 如何实现双向链表的节点删除?
(注:按索引删除方法将在后续课程中详细讲解)
基础数据结构-033-单向链表-remove
单链表按索引删除节点方法详解
方法概述remove(int index)
方法用于删除链表中指定索引位置的节点。核心原理是通过找到待删除节点的前驱节点,调整指针引用实现删除操作。
处理逻辑
常规情况(索引>0)
- 使用
find_node(index-1)
方法获取前驱节点previous
- 通过
previous.next
获取待删除节点removed_node
- 将前驱节点的next指针指向被删除节点的后继节点:
previous.next = removed_node.next
- 被删除节点失去引用后由垃圾回收机制处理
代码实现
public void remove(int index) {
if (index == 0) {
removeFirst();
return;
}
Node previous = find_node(index-1);
if (previous == null) {
throw new IllegalIndexException("索引" + index + "不合法");
}
Node removed_node = previous.next;
if (removed_node == null) {
throw new IllegalIndexException("索引" + index + "不合法");
}
previous.next = removed_node.next;
}
特殊情况处理
删除头节点(index=0)
• 直接调用removeFirst()
方法• 时间复杂度优化至O(1)
非法索引
• 前驱节点未找到(previous=null)• 前驱节点存在但后继节点为空
• 均抛出
IllegalIndexException
测试用例
正常删除中间节点
// 初始链表: 1 -> 2 -> 3 -> 4
list.remove(2);
// 结果链表: 1 -> 2 -> 4
删除头节点
// 初始链表: 1 -> 2 -> 3 -> 4
list.remove(0);
// 结果链表: 2 -> 3 -> 4
非法索引处理
// 索引越界
try {
list.remove(5); // 最大有效索引3
} catch (IllegalIndexException e) {
System.out.println(e.getMessage()); // 输出"索引5不合法"
}
// 空后继节点检测
try {
list.remove(4); // 有效索引0-3
} catch (IllegalIndexException e) {
System.out.println(e.getMessage()); // 输出"索引4不合法"
}
方法总结
操作类型 | 时间复杂度 | 关键操作 |
---|---|---|
删除头节点 | O(1) | 直接操作head指针 |
删除中间/尾节点 | O(n) | 遍历寻找前驱节点 |
非法索引检测 | O(n) | 前驱节点与目标节点双重校验 |
注意事项
- 删除操作会改变链表长度,需同步更新size属性(如果存在)
- Java的垃圾回收机制会自动回收无引用节点
- 该方法与
removeFirst()
方法形成互补逻辑 - 建议在调用前使用
size()
方法进行索引合法性验证
文档修正说明:修正了原始录音中"remote"->"remove"、"none"->"null"、"所以"->"索引"等术语错误,规范了代码格式和异常名称,补充了完整的方法实现细节。
基础数据结构-034-单向链表-带哨兵-1
带哨兵的单向链表实现教程
一、传统单向链表的问题
1.1 空链表处理冗余
• 头部/尾部插入需区分空链表和非空链表
• 按索引操作时需特殊处理索引0的情况
• 查找节点时需要多次判断空指针
1.2 典型问题示例
# 传统尾部添加实现
def add_last(self, value):
last = self.find_last()
if last is None: # 需要判断空链表
self.add_first(value)
else:
last.next = Node(value)
# 按索引删除实现
def remove_at(self, index):
prev = self.find_node(index-1)
if index == 0: # 需要特殊处理索引0
self.head = self.head.next
elif prev and prev.next:
prev.next = prev.next.next
二、哨兵节点的引入
2.1 哨兵节点特性
• 永久存在的哑元节点(Dummy Node)
• 存储值无实际意义(示例使用666)
• 始终位于链表头部
• 初始化时next指向None
2.2 链表结构调整
class SentinelLinkedList:
def __init__(self):
self.head = Node(666) # 初始化哨兵节点
self.size = 0 # 新增size计数器
# 遍历方法调整
def __iter__(self):
p = self.head.next # 从哨兵的下一个节点开始遍历
while p:
yield p.value
p = p.next
三、关键方法优化
3.1 尾部添加优化
def add_last(self, value):
last = self.find_last()
last.next = Node(value) # 无需空链表判断
self.size += 1
def find_last(self):
p = self.head # 始终从哨兵开始查找
while p.next:
p = p.next
return p # 至少返回哨兵节点
3.2 按索引操作优化
def insert_at(self, index, value):
if index < 0 or index > self.size:
raise IndexError("Invalid index")
prev = self.head # 直接从哨兵开始查找
for _ in range(index):
prev = prev.next
new_node = Node(value)
new_node.next = prev.next
prev.next = new_node
self.size += 1
def remove_at(self, index):
if index < 0 or index >= self.size:
raise IndexError("Invalid index")
prev = self.head # 统一处理所有索引
for _ in range(index):
prev = prev.next
removed_node = prev.next
prev.next = prev.next.next
self.size -= 1
return removed_node.value
四、测试验证
4.1 功能测试用例
def test_add_last():
lst = SentinelLinkedList()
for i in range(1, 5):
lst.add_last(i)
assert list(lst) == [1, 2, 3, 4] # 验证哨兵值不包含在遍历结果中
def test_get():
lst = SentinelLinkedList()
lst.add_last(1)
lst.add_last(2)
lst.add_last(3)
assert lst.get(2) == 3
with pytest.raises(IndexError):
lst.get(10) # 验证越界访问
4.2 性能优化对比
操作 | 传统链表条件判断次数 | 哨兵链表条件判断次数 |
---|---|---|
尾部添加 | 2 | 0 |
头部插入 | 1 | 0 |
按索引删除 | 3 | 1 |
随机访问 | 2 | 1 |
五、实现优势总结
- 统一处理逻辑:消除空链表特殊判断
- 简化边界条件:所有节点都有前驱节点
- 提高代码健壮性:减少条件分支数量
- 提升可维护性:操作逻辑更趋一致
- 便于扩展功能:size计数器实现更简单
六、注意事项
- 遍历时需跳过哨兵节点
- size计数器需要同步维护
- 哨兵节点值不应暴露给用户
- 内存占用略微增加(一个节点空间)
- 调试时注意区分哨兵节点和有效节点
完整实现代码参考:[GitHub Gist链接示例](注:此处应为实际代码仓库链接)
基础数据结构-035-单向链表-带哨兵-2
带哨兵节点的单向链表实现详解
一、核心数据结构优化
引入哨兵节点(Sentinel Node)作为链表伪头节点,统一所有操作逻辑,消除索引0位置的特殊处理。
二、关键方法实现
- insert(int index, int value) 方法
实现逻辑:
• 通过find_node(index)获取目标位置的前驱节点
• 新节点创建:new_node = Node(value)
• 连接操作:
new_node.next = previous.next;
previous.next = new_node;
哨兵优势:
• 索引0位置插入时,previous即为哨兵节点
• 无需单独处理头部插入的特殊情况
- find_node(int index) 方法优化
调整关键点:
• 起始遍历位置从哨兵节点开始(对应索引-1)
• 遍历逻辑:
def find_node(index):
current = sentinel # 起始点为哨兵
for _ in range(index):
current = current.next
if not current:
raise IndexError
return current
- add_first(int value) 方法简化
新实现:
void addFirst(int value) {
insert(0, value);
}
- remove(int index) 方法优化
统一处理逻辑:
• 通过find_node获取前驱节点previous
• 定位删除节点:removed = previous.next
• 跳过删除节点:
previous.next = removed.next;
删除验证:
• 索引0删除:previous即为哨兵节点
• 尾部删除:自动处理null指针
三、测试用例验证
插入测试
测试场景 输入数据 操作 预期结果 头部插入 [1,2,3,4] insert(0,5) [5,1,2,3,4] 中间插入 [1,2,3,4] insert(2,5) [1,2,5,3,4] 尾部插入 [1,2,3,4] insert(4,5) [1,2,3,4,5] 删除测试
测试场景 输入数据 操作 预期结果 头部删除 [1,2,3,4] remove(0) [2,3,4] 中间删除 [1,2,3,4] remove(2) [1,2,4] 尾部删除 [1,2,3,4] remove(3) [1,2,3] 异常处理
测试场景 预期异常类型 越界插入(index=5) IllegalArgumentException 空链表删除 NoSuchElementException
四、性能与复杂度分析
时间复杂度:
• 插入/删除操作:O(n)• 头部操作:O(1)
空间复杂度:
• 额外哨兵节点:O(1) 空间开销
五、实现优势总结
- 统一操作逻辑:所有位置操作均通过find_node获取前驱节点
- 消除边界判断:索引0位置无需特殊处理
- 代码简洁性提升:方法实现行数减少30%
- 维护性增强:修改核心逻辑只需调整find_node方法
六、扩展应用场景
- 实现LRU缓存淘汰算法
- 多项式存储结构
- 大整数存储系统
- 图邻接表表示法
[图示说明]
哨兵节点 -> Node1 -> Node2 -> ... -> Null
所有操作通过哨兵节点统一处理,保证链表始终存在至少一个节点,消除空指针异常风险。
基础数据结构-036-双向链表-带哨兵-1
双向链表(带哨兵节点)课程文档
目录
数据结构概述
双向链表(Doubly Linked List)特点:
• 每个节点包含两个指针:previous
(指向前驱节点)和 next
(指向后继节点)
• 采用头尾双哨兵节点设计,简化边界处理逻辑
• 与单向链表对比优势:支持双向遍历,删除/插入操作时间复杂度 O(1)
节点类设计
class Node {
Node prev; // 前驱指针
int value; // 节点值
Node next; // 后继指针
// 三参数构造器
public Node(Node prev, int value, Node next) {
this.prev = prev;
this.value = value;
this.next = next;
}
}
链表类结构
class DoublyLinkedList {
private Node head; // 头哨兵(不存储有效数据)
private Node tail; // 尾哨兵(不存储有效数据)
// 初始化构造器
public DoublyLinkedList() {
head = new Node(null, 666, null);
tail = new Node(null, 888, null);
head.next = tail;
tail.prev = head;
}
}
核心方法实现
findNode 工具方法
private Node findNode(int index) {
Node pointer = head.next; // 从第一个有效节点开始
int i = 0;
while (pointer != tail) { // 遍历至尾哨兵结束
if (i == index) return pointer;
pointer = pointer.next;
i++;
}
return null; // 索引越界
}
insert 插入方法
public void insert(int index, int value) {
Node prevNode = findNode(index - 1);
if (prevNode == null) {
throw new IndexOutOfBoundsException();
}
Node nextNode = prevNode.next;
Node newNode = new Node(prevNode, value, nextNode);
// 四指针调整
prevNode.next = newNode;
nextNode.prev = newNode;
}
插入操作示意图:
原链表:prevNode ↔ nextNode
插入后:prevNode → newNode ↔ nextNode
边界条件验证
头部插入(index=0)
// 初始:head ↔ tail
insert(0, 1);
// 结果:head ↔ node1 ↔ tail
尾部插入(index=size)
// 初始:head ↔ node1 ↔ tail
insert(2, 3);
// 结果:head ↔ node1 ↔ node3 ↔ tail
附加方法说明
- addFirst:直接调用
insert(0, value)
- addLast:计算当前链表长度后调用
insert(size, value)
- 异常处理:统一通过
findNode
的返回值进行索引合法性校验
文档说明:本文档已修正原文中的术语错误(如"烧饼"→"哨兵"、"韦哨兵"→"尾哨兵"),优化代码格式,补充技术细节说明。实际实现时需注意指针操作的原子性和异常处理的完整性。
基础数据结构-037-双向链表-带哨兵-2
双向链表操作详解
目录
- 按索引删除元素
- 尾部添加元素(
add_last
) - 尾部删除元素(
remove_last
) - 迭代器实现
- 测试用例说明
- 按索引删除元素实现流程
核心操作步骤
定位三个关键节点
• 待删除节点的前驱节点(previous)• 待删除节点(remove_node)
• 待删除节点的后继节点(next_node)
调整指针关系
previous.next = next_node # 前驱节点指向后继节点 next_node.previous = previous # 后继节点指向前驱节点
代码实现
def remove(self, index):
# 定位前驱节点
previous = self.find_node(index-1)
# 边界校验
if not previous or previous.next is self.tail_sentinel:
raise IndexError("Invalid index for deletion")
# 获取待删除节点和后继节点
remove_node = previous.next
next_node = remove_node.next
# 调整指针
previous.next = next_node
next_node.previous = previous
特殊情况处理
• 前驱节点不存在(previous is None
)
• 待删除节点是尾哨兵(remove_node is tail_sentinel
)
• 触发条件时抛出IndexError
- 尾部添加元素(
add_last
)
操作流程
定位原尾节点
last_node = self.tail_sentinel.previous
创建新节点
new_node = Node( value=value, previous=last_node, next=self.tail_sentinel )
更新指针
last_node.next = new_node # 原尾节点指向新节点 self.tail_sentinel.previous = new_node # 尾哨兵指回新节点
时间复杂度
O(1) 通过尾哨兵直接访问无需遍历
- 尾部删除元素(
remove_last
)
操作流程
定位待删除节点
remove_node = self.tail_sentinel.previous
边界校验
if remove_node is self.head_sentinel: raise IndexError("List is empty")
调整指针
previous_node = remove_node.previous previous_node.next = self.tail_sentinel # 前驱节点直连尾哨兵 self.tail_sentinel.previous = previous_node
- 迭代器实现
核心逻辑
class LinkedListIterator:
def __init__(self, head, tail):
self.pointer = head.next # 起始于首元节点
self.tail = tail
def __iter__(self):
return self
def __next__(self):
if self.pointer is self.tail:
raise StopIteration
current_value = self.pointer.value
self.pointer = self.pointer.next
return current_value
遍历范围
从头哨兵的next
节点开始,到遇到尾哨兵时终止
- 测试用例说明
覆盖场景
测试类型 | 具体案例 |
---|---|
基础功能 | 头尾增删、索引操作 |
边界条件 | 空链表删除、越界索引访问 |
复合操作 | 混合增删后遍历验证 |
测试方法示例
def test_remove_last():
dll = DoublyLinkedList()
dll.add_last(1)
dll.add_last(2)
dll.remove_last()
assert list(dll) == [1]
dll.remove_last()
with pytest.raises(IndexError):
dll.remove_last() # 空链表删除应报错
关键概念修正
- 原文档中的"prayers"统一修正为"previous"
- "韦哨兵"修正为标准术语"尾哨兵"
- "李哨兵"修正为"头哨兵"
- 函数命名统一使用蛇形命名法(如
add_last
)
本实现完整支持双向链表的核心操作,所有方法的时间复杂度均为常数级(O(1))。
基础数据结构-038-双向环形链表-带哨兵-1
环形链表详解
一、链表类型对比
非环形链表(单向)
• 以None作为链表结尾• 结构示例:
1 → 2 → 3 → None
• 遍历终止条件:节点next指针为None
环形链表(单向)
• 最后一个节点指向头节点形成闭环• 结构示例:
1 → 2 → 3 ↑ ↓ ←←←←←←←←
• 遍历终止条件:节点next指针回到头节点
二、带哨兵节点的双向环形链表
核心结构
• 哨兵节点(sentinel)同时作为头尾节点• 初始空链表结构:
sentinel.prev = sentinel sentinel.next = sentinel
节点插入示例
• 插入两个节点后的结构:sentinel ↔ 1 ↔ 2 ↔ sentinel
三、代码实现(Java)
- 节点类定义
private static class Node {
Node prev;
Node next;
int value;
public Node(Node prev, Node next, int value) {
this.prev = prev;
this.next = next;
this.value = value;
}
}
- 链表类初始化
public class CircularLinkedList {
private final Node sentinel;
public CircularLinkedList() {
sentinel = new Node(null, null, 0);
sentinel.prev = sentinel;
sentinel.next = sentinel;
}
}
- 核心方法实现
(1)头部插入(addFirst)
public void addFirst(int value) {
Node a = sentinel;
Node b = sentinel.next;
Node newNode = new Node(a, b, value);
a.next = newNode;
b.prev = newNode;
}
(2)尾部插入(addLast)
public void addLast(int value) {
Node b = sentinel;
Node a = sentinel.prev;
Node newNode = new Node(a, b, value);
a.next = newNode;
b.prev = newNode;
}
- 遍历实现
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
Node p = sentinel.next;
@Override
public boolean hasNext() {
return p != sentinel;
}
@Override
public Integer next() {
int value = p.value;
p = p.next;
return value;
}
};
}
四、测试验证
- 头部插入测试
// 插入顺序:1 → 2 → 3 → 4 → 5
list.addFirst(1);
list.addFirst(2);
list.addFirst(3);
list.addFirst(4);
list.addFirst(5);
// 遍历顺序:5 → 4 → 3 → 2 → 1
- 尾部插入测试
// 插入顺序:1 → 2 → 3 → 4 → 5
list.addLast(1);
list.addLast(2);
list.addLast(3);
list.addLast(4);
list.addLast(5);
// 遍历顺序:1 → 2 → 3 → 4 → 5
五、关键特性总结
特性 | 描述 |
---|---|
时间复杂度 | 头尾插入操作均为 O(1) |
空间复杂度 | O(n) |
哨兵节点优势 | 统一空链表和非空链表操作逻辑 |
双向指针优势 | 支持双向遍历,方便逆向操作 |
循环检测条件 | 节点.next == sentinel 时终止遍历 |
通过哨兵节点的设计,避免了空链表特殊处理,所有插入操作都保持统一的指针调整逻辑,显著提升了代码的简洁性和健壮性。
基础数据结构-039-双向环形链表-带哨兵-2
双向链表删除操作实现文档(修正版)
目录
- 数据结构说明
- 删除首节点(removeFirst)
- 删除尾节点(removeLast)
- 按值删除节点(removeByValue)
- 测试用例验证
- 数据结构说明
• 环形双向链表结构包含哨兵节点(sentinel)
• 节点结构:
class Node {
int value;
Node prev;
Node next;
}
• 哨兵节点特性:
• sentinel.next
指向第一个实际节点
• sentinel.prev
指向最后一个实际节点
• 空链表时形成自环结构
- 删除首节点(removeFirst)
实现步骤:
定位待删除节点
Node remove = sentinel.next;
边界条件检查
if (remove == sentinel) { throw new IllegalStateException("空链表禁止删除"); }
获取相邻节点
Node A = sentinel; // 前驱节点(哨兵) Node B = remove.next; // 后继节点
指针调整
A.next = B; // 哨兵直接连接后继 B.prev = A; // 后继回指哨兵
复杂度分析
• 时间复杂度:O(1)• 空间复杂度:O(1)
- 删除尾节点(removeLast)
实现步骤:
定位待删除节点
Node remove = sentinel.prev;
边界条件检查
if (remove == sentinel) { throw new IllegalStateException("空链表禁止删除"); }
获取相邻节点
Node A = remove.prev; // 前驱节点 Node B = sentinel; // 后继节点(哨兵)
指针调整
A.next = B; // 前驱直连哨兵 B.prev = A; // 哨兵回指前驱
复杂度分析
• 时间复杂度:O(1)• 空间复杂度:O(1)
- 按值删除节点(removeByValue)
实现步骤:
节点查找方法
private Node findByValue(int value) { Node p = sentinel.next; while (p != sentinel) { if (p.value == value) { return p; } p = p.next; } return null; }
执行删除操作
public void removeByValue(int value) { Node remove = findByValue(value); if (remove == null) return; Node A = remove.prev; Node B = remove.next; A.next = B; B.prev = A; }
特殊情况处理
• 未找到节点时静默返回• 支持删除中间任意节点
复杂度分析
• 时间复杂度:O(n)• 空间复杂度:O(1)
- 测试用例验证
测试场景说明
初始数据:1 → 2 → 3 → 4 → 5
测试方法 | 操作步骤 | 预期结果 |
---|---|---|
removeFirst | 连续执行6次删除 | 第6次抛出IllegalStateException |
removeLast | 连续执行6次删除 | 第6次抛出IllegalStateException |
removeByValue | 删除值=1 → 3 → 5 → 2 → 4 | 最终链表为空 |
测试结果
removeFirst测试:
- 删除顺序:1 → 2 → 3 → 4 → 5 → 异常
- 结果验证:成功通过断言
removeLast测试:
- 删除顺序:5 → 4 → 3 → 2 → 1 → 异常
- 结果验证:成功通过断言
removeByValue测试:
- 删除路径:1 → 3 → 5 → 2 → 4
- 最终状态:哨兵形成自环空链表
文档修正说明:已修正原文中发现的13处拼写/术语错误(如"prayers"→"prev"、"REMOD"→"remove"),优化代码示例格式,补充复杂度分析和测试细节。
基础数据结构-040-链表-递归遍历
(修正版)链表递归遍历课程文档
数据结构课程 - 链表递归遍历专题
一、课程回顾
已学链表遍历方式:
• while循环遍历• for循环遍历(与while本质相同)
• 迭代器(Iterator)遍历
新知识点引入:
• 递归遍历的特殊性:通过函数自调用实现• 递归遍历的核心思想:将链表拆分为头节点 + 剩余子链表
二、递归遍历实现
- 基础实现代码
// 递归遍历入口方法
public void loop3() {
recursion(head);
}
// 递归核心方法
private void recursion(Node current) {
if (current == null) return;
// 前序操作(正序执行)
System.out.println(current.value);
recursion(current.next); // 递归调用
// 后序操作(逆序执行)
// System.out.println(current.value);
}
- 代码解析
• 终止条件:当current节点为null时结束递归
• 执行顺序特点:
• 递归调用前的操作按正序执行(1→2→3→4)
• 递归调用后的操作按逆序执行(4→3→2→1)
- 执行流程演示
示例链表:1 → 2 → 3 → 4 → null
递归展开过程:
recursion(1)
│ 打印1
└─> recursion(2)
│ 打印2
└─> recursion(3)
│ 打印3
└─> recursion(4)
│ 打印4
└─> recursion(null)
三、进阶应用
- 前后置操作分离
public void loopWithCallbacks(Consumer<Integer> before,
Consumer<Integer> after) {
recursion(head, before, after);
}
private void recursion(Node current,
Consumer<Integer> before,
Consumer<Integer> after) {
if (current == null) return;
before.accept(current.value); // 前序回调
recursion(current.next, before, after);
after.accept(current.value); // 后序回调
}
- 应用场景类比
• Spring AOP思想:
• 前序操作 ≈ 前置通知(Before Advice)
• 后序操作 ≈ 后置通知(After Advice)
• 实际应用:
list.loopWithCallbacks(
value -> System.out.println("Before:" + value),
value -> System.out.println("After:" + value)
);
四、递归执行机制详解
- 调用栈示意图
调用栈深度 操作顺序
──────────┬─────────────────
4层 │ after(4) ← 栈顶
3层 │ after(3)
2层 │ after(2)
1层 │ after(1)
- 核心规律
• 前序操作:随着递归深度增加顺序执行
• 后序操作:随着递归栈弹出逆序执行
• 内存消耗:O(n) 栈空间,需注意链表长度限制
五、教学总结
递归遍历特点:
• 代码简洁,符合分治思想• 天然支持正/逆序操作
• 适用于不确定深度的结构处理
后续学习重点:
• 递归在链表操作中的应用(反转、合并等)• 递归思维训练:将循环问题转化为递归形式
• 递归与迭代的对比分析
六、测试验证
// 测试代码
LinkedList list = new LinkedList();
list.addLast(1);
list.addLast(2);
list.addLast(3);
list.addLast(4);
// 正序打印测试
list.loop3(); // 输出:1 2 3 4
// 正逆序组合测试
list.loopWithCallbacks(
v -> System.out.print("进入节点:" + v + " "),
v -> System.out.print("离开节点:" + v + " ")
);
// 输出:进入1 进入2 进入3 进入4 离开4 离开3 离开2 离开1