🔄 循环链表探索

Circular Linked List - 首尾相连的环形数据结构

📖 基本概念

循环链表(Circular Linked List)是一种特殊的链式数据结构,其最后一个节点的指针不是指向NULL,而是指回链表的第一个节点,从而形成一个闭环。这种"首尾相连"的特性使得从任意节点出发都能遍历整个链表。

核心特征

与普通链表的区别

普通单链表的最后一个节点指向NULL,遍历到末尾即停止;而循环链表没有明确的终点,遍历时需要通过判断是否回到起始节点来确定是否完成一轮遍历。

📂 类型分类

1. 单向循环链表

每个节点只有一个指向下一个节点的指针,最后一个节点指向头节点。结构简单,内存占用小。

A B C D HEAD

2. 双向循环链表

每个节点有两个指针,分别指向前驱和后继节点。头节点的prev指向尾节点,尾节点的next指向头节点,形成双向闭环。

A B C

🔗 节点结构

单向循环链表节点

// 单向循环链表节点结构 struct Node { int data; // 数据域:存储节点数据 Node* next; // 指针域:指向下一个节点 }; // 特点:最后一个节点的next指向头节点 // tail->next == head

双向循环链表节点

// 双向循环链表节点结构 struct DNode { int data; // 数据域 DNode* prev; // 前驱指针:指向前一个节点 DNode* next; // 后继指针:指向下一个节点 }; // 特点:形成双向闭环 // head->prev == tail // tail->next == head

⚙️ 基本操作

🎮 交互演示

通过下方的交互工具,直观体验循环链表的基本操作:

链表为空,请添加节点...

💻 代码实现

JavaScript 实现

class Node { constructor(data) { this.data = data; this.next = null; } } class CircularLinkedList { constructor() { this.head = null; this.size = 0; } // 在尾部插入节点 append(data) { const newNode = new Node(data); if (!this.head) { this.head = newNode; newNode.next = this.head; // 指向自己形成环 } else { let current = this.head; while (current.next !== this.head) { current = current.next; } current.next = newNode; newNode.next = this.head; } this.size++; } // 删除指定值的节点 remove(data) { if (!this.head) return false; let current = this.head; let prev = null; // 找到尾节点 while (current.next !== this.head) { prev = current; current = current.next; } prev = current; // prev现在是尾节点 current = this.head; do { if (current.data === data) { if (this.size === 1) { this.head = null; } else if (current === this.head) { prev.next = current.next; this.head = current.next; } else { prev.next = current.next; } this.size--; return true; } prev = current; current = current.next; } while (current !== this.head); return false; } // 查找节点 search(data) { if (!this.head) return -1; let current = this.head; let index = 0; do { if (current.data === data) return index; current = current.next; index++; } while (current !== this.head); return -1; } // 遍历打印 traverse() { if (!this.head) return "Empty List"; let result = []; let current = this.head; do { result.push(current.data); current = current.next; } while (current !== this.head); return result.join(" → ") + " → (回到头节点)"; } }

C语言实现(核心函数)

#include <stdio.h> #include <stdlib.h> typedef struct Node { int data; struct Node* next; } Node; // 创建新节点 Node* createNode(int data) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; newNode->next = NULL; return newNode; } // 在尾部插入 void append(Node** head, int data) { Node* newNode = createNode(data); if (*head == NULL) { *head = newNode; newNode->next = *head; return; } Node* temp = *head; while (temp->next != *head) { temp = temp->next; } temp->next = newNode; newNode->next = *head; } // 遍历链表 void traverse(Node* head) { if (head == NULL) { printf("Empty List\n"); return; } Node* temp = head; do { printf("%d → ", temp->data); temp = temp->next; } while (temp != head); printf("(back to head)\n"); }

📊 时间复杂度分析

头部插入
O(n)
需找到尾节点
尾部插入
O(n)
需遍历到尾部
删除节点
O(n)
需查找目标
查找元素
O(n)
最坏遍历全部
获取长度
O(n)
需完整遍历
空间复杂度
O(n)
n个节点

优化提示:如果维护一个尾指针(tail),可以将尾部插入和头部插入的时间复杂度优化到 O(1)。

⚖️ 与其他链表对比

特性 单链表 循环链表 双向链表 双向循环链表
尾节点指向 NULL 头节点 NULL 头节点
头节点prev 无 无 NULL 尾节点
反向遍历 不支持 不支持 支持 支持
循环访问 需重置 天然支持 需重置 天然支持
空间开销 最小 最小 中等 中等
实现复杂度 简单 中等 中等 较复杂

🎯 应用场景

🖥️

操作系统进程调度

CPU时间片轮转调度算法(Round Robin),多个进程循环获得执行时间,公平分配CPU资源。

🎵

音乐播放器循环

播放列表循环播放功能,当播放到最后一首歌时自动回到第一首继续播放。

🎮

游戏回合制系统

多人游戏中玩家轮流行动,回合结束后自动循环到下一个玩家。

📡

网络缓冲区管理

环形缓冲区(Ring Buffer)用于数据流的高效读写,常用于音视频流处理。

🔄

约瑟夫环问题

经典的数学问题,n个人围成一圈,从第一个人开始报数,数到m的人出局,循环直到剩余一人。

📸

图片轮播组件

网页轮播图自动循环展示,最后一张图片无缝切换到第一张。

⚠️ 注意事项