目录
📊 链表三剑客:特性全景对比表
一、循环链表节点类
二、循环链表的整体设计框架
三、循环列表中的重要方法:
(1)头插法,在头结点前面插入新的节点
(2)尾插法实现插入元素:
(3)删除头结点:
(4)删除第一个指定数据的节点
(5)检查链表中是否存在指定数据这个就简单了,直接遍历,找到了就返回true没找到就返回false
(6) 更新节点值
(7)实现一个迭代器,方便遍历链表元素
(8)在指定索引插入值
四、测试
五、总结
循环链表核心解析
1. 结构特性
2. 操作逻辑与实现要点
节点插入
节点删除
3. 复杂度与性能
4. 应用场景
5. 边界处理与易错点
6. 对比与选型
7. 设计启示
前面我们已经会晤了单向链表与双向链表,今天我们来会会循环链表,其实循环链表就是将单向链表的尾指针指向了头结点,那么如何保证不死循环呢,我们一起来看看吧。
循环链表是一种特殊的链表结构,其尾节点的指针不再指向null,而是指向头节点形成闭环:
-
单向循环链表:尾节点.next = 头节点
-
双向循环链表:尾节点.next = 头节点,头节点.prev = 尾节点
如果关于循环链表还有不了解的读者可以先去看下这篇文章:
线性表的说明
三种链表的对比:
📊 链表三剑客:特性全景对比表
对比维度 | 单向链表 | 双向链表 | 循环链表 |
---|---|---|---|
结构示意图 | A → B → C → null | ←A ↔ B ↔ C→ | A → B → C → [HEAD] |
指针方向 | 单方向(Next) | 双方向(Prev/Next) | 单/双方向 + 闭环 |
头节点访问 | O(1) | O(1) | O(1) |
尾节点访问 | O(n) | O(1)(维护尾指针时) | O(n)(可通过设计优化到O(1)) |
插入操作 | 头插O(1),尾插O(n) | 头尾插入均可O(1) | 头尾插入均可O(n)(需维护闭环) |
删除操作 | 需要前驱节点(平均O(n)) | 可直接删除(O(1)) | 类似单向链表但需维护闭环 |
内存开销 | 最低(每个节点1指针) | 较高(每个节点2指针) | 与单向相同,但需额外闭环指针 |
遍历方向 | 单向 | 双向 | 单向/双向 + 循环 |
核心优势 | 结构简单,内存高效 | 快速反向遍历,删除高效 | 天然支持循环操作 |
经典应用场景 | 栈、简单队列 | LRU缓存、浏览器历史记录 | 轮询调度、音乐循环播放 |
边界处理复杂度 | 简单(只需判断null) | 中等(需处理双向指针) | 较高(闭环维护易出错) |
代码示例特征 | while (current != null) | node.Prev.Next = node.Next | do {...} while (current != head) |
一、循环链表节点类
/// <summary>/// 循环链表节点类/// </summary>/// <typeparam name="T">节点数据类型</typeparam>public class CircularNode<T>{/// <summary>/// 节点存储的数据/// </summary>public T Data { get; set; }/// <summary>/// 指向下一个节点的指针/// </summary>public CircularNode<T> Next { get; set; }/// <summary>/// 节点构造函数/// </summary>/// <param name="data">节点初始数据</param>/// <remarks>/// 初始化时将Next指向自身,形成最小闭环/// 当链表只有一个节点时,构成自环结构/// </remarks>public CircularNode(T data){Data = data;Next = this; // 关键闭环操作}}
二、循环链表的整体设计框架
/// <summary>
/// 单向循环链表实现类
/// </summary>
/// <typeparam name="T">链表元素类型</typeparam>
public class CircularLinkedList<T>
{/// <summary>/// 链表头节点(关键指针)/// </summary>private CircularNode<T> head;/// <summary>/// 节点计数器(优化统计效率)/// </summary>private int count;/// <summary>/// 链表元素数量(O(1)时间复杂度)/// </summary>public int Count => count;/// <summary>/// 判断链表是否为空/// </summary>public bool IsEmpty => head == null;/// <summary>/// 打印链表内容(调试用方法)/// </summary>public void PrintAll(){if (head == null){Console.WriteLine("[Empty List]");return;}var current = head;do{Console.Write($"{current.Data} -> ");current = current.Next;} while (current != head); // 循环终止条件判断Console.WriteLine("[HEAD]"); // 闭环标记}
}
三、循环列表中的重要方法:
(1)头插法,在头结点前面插入新的节点
假设我们有这样一个循环链表:
那么我们如何利用头插法进行插入新节点呢:
①先创建一个新节点
②判断当前链表是否为空,为空则将当前新节点置为头结点
③当前链表存在,则,首先找到尾节点,然后将新节点指向原先的头结点,接着将新节点覆盖原先头结点,最后将尾节点指向新的头结点即可:如下图所示
④计数器++
代码实现:
/// <summary>
/// 在链表头部插入新节点
/// </summary>
/// <param name="data">插入数据</param>
/// <remarks>
/// 时间复杂度:O(n)(需要遍历找到尾节点)
/// 特殊情况处理:
/// 1. 空链表插入
/// 2. 单节点链表插入
/// 3. 多节点链表插入
/// </remarks>
public void AddFirst(T data)
{var newNode = new CircularNode<T>(data);if (head == null){// 空链表情况处理head = newNode;}else{// 查找当前尾节点(关键步骤)var tail = head;while (tail.Next != head){tail = tail.Next;}// 新节点指向原头节点newNode.Next = head;// 更新头指针head = newNode;// 更新尾节点指向新头(维持闭环)tail.Next = head;}count++; // 更新计数器
}
(2)尾插法实现插入元素:
思想:
①创建一个新节点
②如果当前链表为空,则将当前头结点设置为新节点,然后指向自己,闭环
③当前节点不为空,首先找到尾节点,接着将尾节点指向新节点,然后将新节点指向头结点,完毕!
④计数器++
实现代码:
/// <summary>
/// 在链表尾部插入新节点
/// </summary>
/// <param name="data">插入数据</param>
/// <remarks>
/// 时间复杂度:O(n)(需要遍历到尾部)
/// 优化思路:可以维护尾指针变量将时间复杂度降为O(1)
/// </remarks>
public void AddLast(T data)
{var newNode = new CircularNode<T>(data);if (head == null){// 空链表处理head = newNode;head.Next = head; // 自环处理}else{// 查找当前尾节点var tail = head;while (tail.Next != head){tail = tail.Next;}// 新节点指向头节点newNode.Next = head;// 当前尾节点指向新节点tail.Next = newNode;}count++;
}
(3)删除头结点:
①:判断列表是否有值,没有删除就是错误手段,应抛出错误
②:判断是否只有一个节点,是的话直接置空
③:多节点时,先找到尾节点,将头结点更新为头结点的下一个,将尾节点指向新的头结点
代码实现:
④:计数器--
/// <summary>
/// 删除链表头节点
/// </summary>
/// <exception cref="InvalidOperationException">空链表删除时抛出异常</exception>
/// <remarks>
/// 重点处理:
/// 1. 空链表异常
/// 2. 单节点链表删除
/// 3. 多节点链表删除
/// </remarks>
public void RemoveFirst()
{if (head == null)throw new InvalidOperationException("Cannot remove from empty list");if (head.Next == head) // 单节点判断条件{// 清除头节点引用head = null;}else{// 查找当前尾节点var tail = head;while (tail.Next != head){tail = tail.Next;}// 移动头指针到下一节点head = head.Next;// 更新尾节点指向新头tail.Next = head;}count--; // 更新计数器
}
(4)删除第一个指定数据的节点
①:先判断是否为空链表,为空直接抛出错误
②:然后准备两个临时节点,一个是current,一个是previous。还有一个标志位:found。
current用来记录当前节点,在链表中一直跑跑跑,如果找到了目标值,就直接退出循环;还有就是当current的下一个节点是头结点时,说明此时已经到了尾节点,如果此刻的值还不等于,说明就是没找到。
previous是为了判断当前节点是否为头结点,那怎么判断是不是头结点呢,我们首先会将previous置空,current每次往后移动一个节点,然后将previous用current覆盖,如果在经历了current遍历链表之后,previous还是空, 说明什么?说明目标值就是头结点,那么此刻就是删除头结点的操作;
删除操作:1)如果删除的头结点,要进行判断是否是单节点,是的话直接置空,不是的话要找到尾节点,然后将重复上面的删除头结点操作
2)删除的不是头结点,就直接将previous的下一个指向current的下一个就好,因为你的previous是当前目标节点的前一个,你想删除当前节点,那么是不是就是将前一个节点的下一个指向当前目标节点的下一个,这样自己就被删除了。这里我们还要加一个特殊判断,就是如果当前节点是历史头结点,那么需要更新这个头结点
代码如下:
/// <summary>
/// 删除第一个匹配的节点
/// </summary>
/// <param name="data">要删除的数据</param>
/// <returns>是否成功删除</returns>
/// <remarks>
/// 关键点:
/// 1. 循环遍历时的终止条件
/// 2. 头节点删除的特殊处理
/// 3. 单节点链表的处理
/// </remarks>
public bool Remove(T data)
{if (head == null) return false;CircularNode<T> current = head;CircularNode<T>? previous = null;bool found = false;// 使用do-while确保至少执行一次循环do{if (EqualityComparer<T>.Default.Equals(current.Data, data)){found = true;break;}previous = current;current = current.Next;} while (current != head);if (!found) return false;// 删除节点逻辑if (previous == null) // 删除的是头节点{if (head.Next == head) // 唯一节点情况{head = null;}else{// 查找当前尾节点var tail = head;while (tail.Next != head){tail = tail.Next;}// 移动头指针head = head.Next;// 更新尾节点指向新头tail.Next = head;}}else // 删除中间或尾部节点{previous.Next = current.Next;// 如果删除的是原头节点(current == head)if (current == head) // 防御性检查{head = previous.Next; // 强制更新头指针}}count--;return true;
}
(5)检查链表中是否存在指定数据
这个就简单了,直接遍历,找到了就返回true没找到就返回false
代码如下:
/// <summary>
/// 检查链表中是否存在指定数据
/// </summary>
/// <param name="data">查找目标数据</param>
/// <returns>存在返回true</returns>
/// <remarks>
/// 使用值相等比较(EqualityComparer.Default)
/// 注意:对于引用类型需要正确实现Equals方法
/// </remarks>
public bool Contains(T data)
{if (head == null) return false;var current = head;do{if (EqualityComparer<T>.Default.Equals(current.Data, data)){return true;}current = current.Next;} while (current != head); // 完整遍历一圈return false;
}
(6) 更新节点值
这个在上面查找的基础上直接修改值就行了:
/// <summary>
/// 修改第一个匹配的节点值
/// </summary>
/// <param name="oldValue">旧值</param>
/// <param name="newValue">新值</param>
/// <returns>修改成功返回true</returns>
/// <remarks>
/// 注意:此方法直接修改节点数据引用
/// 如果节点存储的是引用类型,需要注意副作用
/// </remarks>
public bool Update(T oldValue, T newValue)
{if (head == null) return false;var current = head;do{if (EqualityComparer<T>.Default.Equals(current.Data, oldValue)){current.Data = newValue; // 直接修改数据引用return true;}current = current.Next;} while (current != head);return false;
}
(7)实现一个迭代器,方便遍历链表元素
/// <summary>
/// 实现迭代器
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{if (head == null) yield break;var current = head;do{yield return current.Data;current = current.Next;} while (current != head);
}
(8)在指定索引插入值
①:先判断索引值是否合理,不合理直接抛出错误
②:判断是否在第一个位置插入,是的话,直接调用AddFirst();
③:判断是否在最后一个位置插入,是的话直接调用AddLast();
④:for循环,找到索引位置的前一个,将当前节点的下一个节点值存起来,然后指向新结点,最后将新节点指向下一个节点
代码如下:
⑤:计数器++
/// <summary>/// 在指定索引位置插入节点/// </summary>/// <param name="index">插入位置(0-based)</param>/// <param name="data">插入数据</param>/// <exception cref="ArgumentOutOfRangeException">索引越界时抛出</exception>/// <remarks>/// 索引有效性检查:/// - index < 0 或 index > count 时抛出异常/// 当index=0时等价于AddFirst/// 当index=count时等价于AddLast/// </remarks>public void InsertAt(int index, T data){if (index < 0 || index > count)throw new ArgumentOutOfRangeException(nameof(index));if (index == 0){AddFirst(data);return;}if (index == count){AddLast(data);return;}var newNode = new CircularNode<T>(data);var current = head;// 移动到插入位置前驱节点for (int i = 0; i < index - 1; i++){current = current.Next;}// 插入新节点newNode.Next = current.Next;current.Next = newNode;count++;}
四、测试
internal class Program{static void Main(string[] args){// 初始化循环链表var playlist = new CircularLinkedList<string>();Console.WriteLine($"新建播放列表,是否为空:{playlist.IsEmpty}");// 添加歌曲(混合使用头插和尾插)playlist.AddFirst("晴天 - 周杰伦");playlist.AddLast("七里香 - 周杰伦");playlist.AddFirst("夜曲 - 周杰伦");playlist.PrintAll(); // 输出:夜曲 -> 晴天 -> 七里香 -> [HEAD]// 插入操作playlist.InsertAt(1, "稻香 - 周杰伦");Console.WriteLine("\n插入新歌曲后:");playlist.PrintAll(); // 输出:夜曲 -> 稻香 -> 晴天 -> 七里香 -> [HEAD]// 删除操作playlist.RemoveFirst();Console.WriteLine("\n删除首曲后:");playlist.PrintAll(); // 输出:稻香 -> 晴天 -> 七里香 -> [HEAD]bool removed = playlist.Remove("晴天 - 周杰伦");Console.WriteLine($"\n删除晴天结果:{removed}");playlist.PrintAll(); // 输出:稻香 -> 七里香 -> [HEAD]// 查找测试bool exists = playlist.Contains("七里香 - 周杰伦");Console.WriteLine($"\n是否包含七里香:{exists}"); // 输出:True// 更新操作bool updated = playlist.Update("稻香 - 周杰伦", "稻香(Remix版) - 周杰伦");Console.WriteLine($"\n更新稻香结果:{updated}");playlist.PrintAll(); // 输出:稻香(Remix版) -> 七里香 -> [HEAD]// 边界测试:删除最后一个节点playlist.Remove("七里香 - 周杰伦");Console.WriteLine("\n删除七里香后:");playlist.PrintAll(); // 输出:稻香(Remix版) -> [HEAD]// 异常处理测试try{var emptyList = new CircularLinkedList<int>();emptyList.RemoveFirst(); // 触发异常}catch (InvalidOperationException ex){Console.WriteLine($"\n异常捕获:{ex.Message}");}// 使用迭代器遍历Console.WriteLine("\n当前播放列表循环播放:");foreach (var song in playlist){Console.WriteLine($"正在播放:{song}");}}}
测试结果:
五、总结
循环链表核心解析
1. 结构特性
循环链表是一种首尾相连的链式结构,其核心特征为:
- 闭环设计:尾节点的指针不再指向空值,而是指向头节点,形成环形链路。
- 自洽节点:每个新节点创建时默认指向自身,即使链表仅有一个节点也能维持闭合回路。
- 遍历特性:从任意节点出发均可遍历整个链表,没有传统链表的“终点”概念。
2. 操作逻辑与实现要点
节点插入
-
头插法
新节点成为链表的起点:- 若链表为空,新节点自环即为头节点。
- 若链表非空,需先找到尾节点(遍历至
Next
指向头节点的节点)。 - 新节点的
Next
指向原头节点,尾节点的Next
更新为新节点,头指针重置为新节点。
耗时:O(n)(查找尾节点),维护尾指针可优化至 O(1)。
-
尾插法
新节点成为链表的终点:- 若链表为空,处理逻辑同头插法。
- 若链表非空,遍历找到尾节点后,使其
Next
指向新节点,新节点的Next
指向头节点。
耗时:O(n),维护尾指针可优化。
节点删除
-
删除头节点
- 单节点链表:直接置空头指针。
- 多节点链表:查找尾节点,将其
Next
指向原头节点的下一节点,更新头指针。
关键:确保尾节点与新头节点的连接,避免闭环断裂。
-
删除指定节点
- 遍历链表匹配目标值,记录前驱节点。
- 若目标为头节点:按头节点删除逻辑处理。
- 若为中间节点:前驱节点的
Next
跳过目标节点,直接指向其后继节点。
注意:删除后需校验头指针是否失效,防止逻辑错误。
3. 复杂度与性能
-
时间复杂度
- 基础操作(头插、尾插、删除):默认 O(n),因需查找尾节点或遍历匹配。
- 优化策略:维护尾指针变量,可将头尾操作降至 O(1)。
- 查询与修改:O(n),需遍历至目标位置。
-
空间复杂度
与单向链表一致,每个节点仅需存储数据和单个指针,无额外内存负担。
4. 应用场景
-
循环任务调度
如操作系统的轮询机制,循环链表可自然支持任务队列的循环执行。 -
多媒体播放控制
音乐播放器的“循环播放”模式,通过链表闭环实现歌曲无缝衔接。 -
游戏逻辑设计
多玩家回合制游戏中,循环链表可管理玩家顺序,实现循环回合。 -
资源池管理
数据库连接池等场景,循环分配资源时可通过链表快速定位下一个可用资源。
5. 边界处理与易错点
-
空链表操作
插入首个节点时需维护自环,删除操作前必须检查链表是否为空,避免空指针异常。 -
单节点维护
删除仅有的节点后,需及时置空头指针,防止遗留无效引用。 -
循环终止条件
遍历时使用do-while
结构,确保至少访问头节点一次,终止条件为回到起始点。 -
闭环完整性
任何操作后需验证尾节点的Next
是否指向头节点,防止闭环断裂导致死循环。
6. 对比与选型
-
VS 单向链表
- 优势:天然支持循环访问,尾节点操作更易优化。
- 劣势:删除非头节点时仍需遍历,代码复杂度稍高。
-
VS 双向链表
- 优势:内存占用更低,适合单向循环足够使用的场景。
- 劣势:无法快速反向遍历,中间节点删除效率较低。
7. 设计启示
-
扩展性考量
可增加Tail
指针变量,将尾节点访问从 O(n) 优化至 O(1),提升高频尾插场景性能。 -
迭代器安全
实现自定义迭代器时,需处理链表在遍历过程中被修改的情况,避免并发冲突。 -
数据一致性
节点删除后应及时更新计数器(count
),确保Count
属性准确反映实际长度。
好的呀。我们终于结束了关于链表的知识了!继续前进!
附本文所有代码:
namespace 循环链表
{/// <summary>/// 循环链表节点类/// </summary>/// <typeparam name="T">节点数据类型</typeparam>public class CircularNode<T>{/// <summary>/// 节点存储的数据/// </summary>public T Data { get; set; }/// <summary>/// 指向下一个节点的指针/// </summary>public CircularNode<T> Next { get; set; }/// <summary>/// 节点构造函数/// </summary>/// <param name="data">节点初始数据</param>/// <remarks>/// 初始化时将Next指向自身,形成最小闭环/// 当链表只有一个节点时,构成自环结构/// </remarks>public CircularNode(T data){Data = data;Next = this; // 关键闭环操作}}/// <summary>/// 单向循环链表实现类/// </summary>/// <typeparam name="T">链表元素类型</typeparam>public class CircularLinkedList<T>{/// <summary>/// 链表头节点(关键指针)/// </summary>private CircularNode<T>? head;/// <summary>/// 节点计数器(优化统计效率)/// </summary>private int count;/// <summary>/// 链表元素数量(O(1)时间复杂度)/// </summary>public int Count => count;/// <summary>/// 判断链表是否为空/// </summary>public bool IsEmpty => head == null;/// <summary>/// 打印链表内容(调试用方法)/// </summary>public void PrintAll(){if (head == null){Console.WriteLine("[Empty List]");return;}var current = head;do{Console.Write($"{current.Data} -> ");current = current.Next;} while (current != head); // 循环终止条件判断Console.WriteLine("[HEAD]"); // 闭环标记}/// <summary>/// 在链表头部插入新节点/// </summary>/// <param name="data">插入数据</param>/// <remarks>/// 时间复杂度:O(n)(需要遍历找到尾节点)/// 特殊情况处理:/// 1. 空链表插入/// 2. 单节点链表插入/// 3. 多节点链表插入/// </remarks>public void AddFirst(T data){var newNode = new CircularNode<T>(data);if (head == null){// 空链表情况处理head = newNode;}else{// 查找当前尾节点(关键步骤)var tail = head;while (tail.Next != head){tail = tail.Next;}// 新节点指向原头节点newNode.Next = head;// 更新头指针head = newNode;// 更新尾节点指向新头(维持闭环)tail.Next = head;}count++; // 更新计数器}/// <summary>/// 在链表尾部插入新节点/// </summary>/// <param name="data">插入数据</param>/// <remarks>/// 时间复杂度:O(n)(需要遍历到尾部)/// 优化思路:可以维护尾指针变量将时间复杂度降为O(1)/// </remarks>public void AddLast(T data){var newNode = new CircularNode<T>(data);if (head == null){// 空链表处理head = newNode;head.Next = head; // 自环处理}else{// 查找当前尾节点var tail = head;while (tail.Next != head){tail = tail.Next;}// 新节点指向头节点newNode.Next = head;// 当前尾节点指向新节点tail.Next = newNode;}count++;}/// <summary>/// 删除链表头节点/// </summary>/// <exception cref="InvalidOperationException">空链表删除时抛出异常</exception>/// <remarks>/// 重点处理:/// 1. 空链表异常/// 2. 单节点链表删除/// 3. 多节点链表删除/// </remarks>public void RemoveFirst(){if (head == null)throw new InvalidOperationException("Cannot remove from empty list");if (head.Next == head) // 单节点判断条件{// 清除头节点引用head = null;}else{// 查找当前尾节点var tail = head;while (tail.Next != head){tail = tail.Next;}// 移动头指针到下一节点head = head.Next;// 更新尾节点指向新头tail.Next = head;}count--; // 更新计数器}/// <summary>/// 删除第一个匹配的节点/// </summary>/// <param name="data">要删除的数据</param>/// <returns>是否成功删除</returns>/// <remarks>/// 关键点:/// 1. 循环遍历时的终止条件/// 2. 头节点删除的特殊处理/// 3. 单节点链表的处理/// </remarks>public bool Remove(T data){if (head == null) return false;CircularNode<T> current = head;CircularNode<T>? previous = null;bool found = false;// 使用do-while确保至少执行一次循环do{if (EqualityComparer<T>.Default.Equals(current.Data, data)){found = true;break;}previous = current;current = current.Next;} while (current != head);if (!found) return false;// 删除节点逻辑if (previous == null) // 删除的是头节点{if (head.Next == head) // 唯一节点情况{head = null;}else{// 查找当前尾节点var tail = head;while (tail.Next != head){tail = tail.Next;}// 移动头指针head = head.Next;// 更新尾节点指向新头tail.Next = head;}}else // 删除中间或尾部节点{previous.Next = current.Next;// 如果删除的是原头节点(current == head)if (current == head){head = previous.Next; // 更新头指针}}count--;return true;}/// <summary>/// 检查链表中是否存在指定数据/// </summary>/// <param name="data">查找目标数据</param>/// <returns>存在返回true</returns>/// <remarks>/// 使用值相等比较(EqualityComparer.Default)/// 注意:对于引用类型需要正确实现Equals方法/// </remarks>public bool Contains(T data){if (head == null) return false;var current = head;do{if (EqualityComparer<T>.Default.Equals(current.Data, data)){return true;}current = current.Next;} while (current != head); // 完整遍历一圈return false;}/// <summary>/// 修改第一个匹配的节点值/// </summary>/// <param name="oldValue">旧值</param>/// <param name="newValue">新值</param>/// <returns>修改成功返回true</returns>/// <remarks>/// 注意:此方法直接修改节点数据引用/// 如果节点存储的是引用类型,需要注意副作用/// </remarks>public bool Update(T oldValue, T newValue){if (head == null) return false;var current = head;do{if (EqualityComparer<T>.Default.Equals(current.Data, oldValue)){current.Data = newValue; // 直接修改数据引用return true;}current = current.Next;} while (current != head);return false;}/// <summary>/// 在指定索引位置插入节点/// </summary>/// <param name="index">插入位置(0-based)</param>/// <param name="data">插入数据</param>/// <exception cref="ArgumentOutOfRangeException">索引越界时抛出</exception>/// <remarks>/// 索引有效性检查:/// - index < 0 或 index > count 时抛出异常/// 当index=0时等价于AddFirst/// 当index=count时等价于AddLast/// </remarks>public void InsertAt(int index, T data){if (index < 0 || index > count)throw new ArgumentOutOfRangeException(nameof(index));if (index == 0){AddFirst(data);return;}if (index == count){AddLast(data);return;}var newNode = new CircularNode<T>(data);var current = head;// 移动到插入位置前驱节点for (int i = 0; i < index - 1; i++){current = current.Next;}// 插入新节点newNode.Next = current.Next;current.Next = newNode;count++;}/// <summary>/// 实现迭代器/// </summary>/// <returns></returns>public IEnumerator<T> GetEnumerator(){if (head == null) yield break;var current = head;do{yield return current.Data;current = current.Next;} while (current != head);}}internal class Program{static void Main(string[] args){// 初始化循环链表var playlist = new CircularLinkedList<string>();Console.WriteLine($"新建播放列表,是否为空:{playlist.IsEmpty}");// 添加歌曲(混合使用头插和尾插)playlist.AddFirst("晴天 - 周杰伦");playlist.AddLast("七里香 - 周杰伦");playlist.AddFirst("夜曲 - 周杰伦");playlist.PrintAll(); // 输出:夜曲 -> 晴天 -> 七里香 -> [HEAD]// 插入操作playlist.InsertAt(1, "稻香 - 周杰伦");Console.WriteLine("\n插入新歌曲后:");playlist.PrintAll(); // 输出:夜曲 -> 稻香 -> 晴天 -> 七里香 -> [HEAD]// 删除操作playlist.RemoveFirst();Console.WriteLine("\n删除首曲后:");playlist.PrintAll(); // 输出:稻香 -> 晴天 -> 七里香 -> [HEAD]bool removed = playlist.Remove("晴天 - 周杰伦");Console.WriteLine($"\n删除晴天结果:{removed}");playlist.PrintAll(); // 输出:稻香 -> 七里香 -> [HEAD]// 查找测试bool exists = playlist.Contains("七里香 - 周杰伦");Console.WriteLine($"\n是否包含七里香:{exists}"); // 输出:True// 更新操作bool updated = playlist.Update("稻香 - 周杰伦", "稻香(Remix版) - 周杰伦");Console.WriteLine($"\n更新稻香结果:{updated}");playlist.PrintAll(); // 输出:稻香(Remix版) -> 七里香 -> [HEAD]// 边界测试:删除最后一个节点playlist.Remove("七里香 - 周杰伦");Console.WriteLine("\n删除七里香后:");playlist.PrintAll(); // 输出:稻香(Remix版) -> [HEAD]// 异常处理测试try{var emptyList = new CircularLinkedList<int>();emptyList.RemoveFirst(); // 触发异常}catch (InvalidOperationException ex){Console.WriteLine($"\n异常捕获:{ex.Message}");}// 使用迭代器遍历Console.WriteLine("\n当前播放列表循环播放:");foreach (var song in playlist){Console.WriteLine($"正在播放:{song}");}}}
}