← 链表 · Linked List / 循环链表:尾接回头(单 / 双循环) 待审核 4 / 14
circular · 首尾相连

循环链表:尾接回头(单 / 双循环)

普通链表以 null 收尾,尾节点的 next 指向空。循环链表 (circular linked list) 把这根 next 接回头节点,整条链闭合成环——没有终点。于是遍历的判停条件从「碰到 null」变成「回到起点」。环上再加一圈 prev 就是双向循环;若环里再放一个不存数据的哨兵 (sentinel)节点,空环与非空环、定位头尾的逻辑就能统一——这正是 Linux 内核 list_head 的形态。下面先切换三种环形结构看连法,再用增 (insert) 删 (delete) 查 (query) 三类操作的单步演示,看清「无 null 终点」如何改变算法:查找 (query) 即环形遍历,仍是 O(n)O(n),但判停靠「回到 start」而非碰到 null;插入 / 删除的核心是维护环的闭合,改指针要按序进行、别让环断开;哨兵则让头部增删与中间增删统一到同一条代码路径。约瑟夫环 (Josephus) 作为删除的经典应用一并保留。

**判停从「碰到 null」变成「回到起点」。**普通链表遍历写 while (p) p = p.next,靠尾后那个 null 自然停下。循环链表没有 null,若照写就会无限绕圈。正确写法是先记下起点、用 dowhiledo\dots while 至少访问一次、再在 p === start 时收手:do { …; p = p.next } while (p !== start)。判停条件换了一个,是循环链表与普通链表在算法层面最实际的差异。

**双向循环 + 哨兵 = 内核 list_head。**环上放一个不存数据的哨兵节点,带来两点统一:其一,空链表也是「哨兵自己指向自己」的合法环,不必为 head === null 单独写分支;其二,头插 = 哨兵之后插入、尾插 = 哨兵之前插入,都落在「在某节点前 / 后插入」这一条 O(1)O(1) 代码路径上,无需特判头尾。Linux 内核的 struct list_head 正是这种双向循环 + 哨兵布局,内核里随处可见。

循环链表的典型用途:轮转。凡是「转一圈又从头开始」的场景都贴合环形结构——操作系统的时间片轮转 (round-robin)调度让就绪进程排成一个环依次拿 CPU;环形缓冲区 (ring buffer) 用读写两个游标在固定容量上绕圈复用,虽多用数组实现,但「首尾相连、写满覆盖最旧」的语义同出一辙。约瑟夫环则是它最经典的教学模型:n 人围成一圈,从某人起每数 k 个淘汰一人,反复直到剩一人——循环链表正是这道题的天然数据结构。这里的环形删除只是其一,约瑟夫环专页进一步把模拟 / 递推 / k=2 位运算闭式三条解法做成对等可视,看同一答案如何被层层优化算出。