目录
1、介绍
1.1、结构
1.2、循环链表的特点
1.3、循环链表的定义
2、快慢指针法的原理
3、操作过程
4、循环链表的优缺点
4.1、优点
4.2、缺点
1、介绍
循环链表(Circular Linked List)是一种特殊的链表结构,其最后一个节点的指针指向链表的头节点(或某个中间节点),从而形成一个闭环。
这种结构在某些特定场景下非常有用,例如轮询调度、数据缓冲区管理等。
1.1、结构
如下图所示:
要判断一个链表是否是循环的(即存在环),最常用的方法是 快慢指针法。
该方法具有时间复杂度为 O(n),空间复杂度为 O(1) 的优点,非常适合在不额外使用存储空间的情况下检测链表环。
1.2、循环链表的特点
闭环结构:遍历链表时,可以从任意节点出发,最终回到起点。无“尾”概念:没有严格意义上的尾节点(除非手动定义)。操作复杂度:
插入/删除时需要特别注意指针的连接,避免破坏闭环。 适用场景:
轮流调度(如任务调度、游戏角色轮流)缓冲区(如环形缓冲区 Ring Buffer)数据循环访问(如播放列表)
1.3、循环链表的定义
循环链表与普通链表的主要区别在于:
单向循环链表:尾节点的 next 指针指向头节点。双向循环链表:尾节点的 next 指向头节点,头节点的 prev 指向尾节点。
2、快慢指针法的原理
如下图所示:
使用两个指针:慢指针(slow) 每次移动一步,快指针(fast) 每次移动两步。如果链表中存在环,两个指针最终会相遇(因为快指针在环内“追上”了慢指针)。如果链表无环,快指针最终会到达链表的末尾(即 fast 或 fast.next 为 null)。
3、操作过程
初始化两个指针 slow 和 fast 都指向链表的头节点。进入循环:
slow 向前移动一步:slow = slow.nextfast 向前移动两步:fast = fast.next.next 如果 slow == fast,说明链表中存在环,返回 true。如果 fast 或 fast.next 为 null,说明没有环,返回 fasle。
代码示例:
class ListNode {
int val;
ListNode next;
ListNode(int val){
this.val = val;
this.next = null;
}
}
public class ListNodeTest {
/**
* 判断链表是否有环
* @param head
* @return
*/
public boolean hasCycle(ListNode head){
if (head == null || head.next == null){
return false;
}
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null){
// 快指针走两步,慢指针走一步
slow = slow.next;
fast = fast.next.next;
if (slow == fast){
// 有环
return true;
}
}
return false;
}
}
注意事项
空链表或单节点链表:需要处理边界情况,例如 head is None 时直接返回 False。防止空指针异常:在每次移动 fast 之前,必须确保 fast 和 fast.next 不为 null。循环的起点不影响算法:无论环从哪个节点开始,快慢指针总会相遇。
总结
4、循环链表的优缺点
4.1、优点
方便遍历:无需额外标记尾节点即可循环访问。避免空指针:尾节点指向头节点,无需处理 null。适用轮转场景:如任务调度、游戏角色轮流。
4.2、缺点
操作复杂:插入/删除时需要维护闭环。容易出错:指针调整不当会导致死循环或结构破坏。调试困难:错误的指针连接可能难以发现。
总结
✅ 使用建议
如果需要频繁的循环访问或轮转操作,使用循环链表。如果链表操作简单且无需循环,优先使用普通链表。注意避免死循环,确保遍历或操作时设置正确的终止条件。