当前位置: 首页 > article >正文

C#进阶学习(六)单向链表和双向链表,循环链表(下)循环链表

      

目录

📊 链表三剑客:特性全景对比表

一、循环链表节点类

二、循环链表的整体设计框架

三、循环列表中的重要方法:

(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.Nextdo {...} 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每次往后移动一个节点,然后将previouscurrent覆盖,如果在经历了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. 操作逻辑与实现要点
节点插入
  • 头插法
    新节点成为链表的起点:

    1. 若链表为空,新节点自环即为头节点。
    2. 若链表非空,需先找到尾节点(遍历至 Next 指向头节点的节点)。
    3. 新节点的 Next 指向原头节点,尾节点的 Next 更新为新节点,头指针重置为新节点。
      ​耗时​:O(n)(查找尾节点),维护尾指针可优化至 O(1)。
  • 尾插法
    新节点成为链表的终点:

    1. 若链表为空,处理逻辑同头插法。
    2. 若链表非空,遍历找到尾节点后,使其 Next 指向新节点,新节点的 Next 指向头节点。
      ​耗时​:O(n),维护尾指针可优化。
节点删除
  • 删除头节点

    1. 单节点链表:直接置空头指针。
    2. 多节点链表:查找尾节点,将其 Next 指向原头节点的下一节点,更新头指针。
      ​关键​:确保尾节点与新头节点的连接,避免闭环断裂。
  • 删除指定节点

    1. 遍历链表匹配目标值,记录前驱节点。
    2. 若目标为头节点:按头节点删除逻辑处理。
    3. 若为中间节点:前驱节点的 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}");}}}
}

相关文章:

C#进阶学习(六)单向链表和双向链表,循环链表(下)循环链表

目录 &#x1f4ca; 链表三剑客&#xff1a;特性全景对比表 一、循环链表节点类 二、循环链表的整体设计框架 三、循环列表中的重要方法&#xff1a; &#xff08;1&#xff09;头插法&#xff0c;在头结点前面插入新的节点 &#xff08;2&#xff09;尾插法实现插入元素…...

后端程序员工作复盘(一)

1、工作不是为了解决问题&#xff0c;而是为了生活目标。 2、不能当救火队员&#xff0c;要提前预防问题的产生、避免问题的出现。 3、后端表设计和接口设计&#xff0c;要考虑到扩展性&#xff0c;要灵活。无论页面如何变动&#xff0c;后端的改动量都最小&#xff0c;要以不…...

禅道部署进阶指南:从搭建到高可用,全程打怪升级!

禅道在生产环境中的更专业部署方案,包括 Linux 服务器部署、Docker 安装方案、性能优化、安全建议和常见企业级集成方式,适合团队使用或对稳定性、安全性有较高要求的项目。 ✅ 一、企业级部署方案(适合 Linux 环境) 🖥 环境要求 操作系统:CentOS 7+/Ubuntu 18+(推荐)…...

文章记单词 | 第36篇(六级)

一&#xff0c;单词释义 wit [wɪt] n. 智慧&#xff1b;才智&#xff1b;机智&#xff1b;风趣的人dreadful [ˈdredfl] adj. 糟糕透顶的&#xff1b;可怕的&#xff1b;令人畏惧的innocent [ˈɪnəsnt] adj. 无辜的&#xff1b;天真无邪的&#xff1b;无罪的&#xff1b;无…...

Unity使用Newtonsoft.Json本地化存档

我是标题 1.依赖包2.原理&#xff1a;3.代码4.可用优化5.数据加密 1.依赖包 Newtonsoft请在PacakgeManager处下载。 参考&#xff1a;打工人小棋 2.原理&#xff1a; 把要存储的对象数据等使用JsonConvert.SerializeObject(object T)进行序列化为字符串&#xff0c;并且通过…...

Java研学-MybatisPlus(一)

一 概述 MyBatis-Plus&#xff08;简称 MP&#xff09;是一款基于 MyBatis 的增强工具&#xff0c;旨在简化开发、提高效率。它在保留 MyBatis 所有特性的基础上&#xff0c;提供了丰富的功能&#xff0c;减少了大量模板代码的编写。 1 核心特性&#xff1a; ① 无侵入增强&am…...

2025年03月中国电子学会青少年软件编程(Python)等级考试试卷(六级)真题

青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;六级&#xff09; 分数&#xff1a;100 题数&#xff1a;38 答案解析&#xff1a;https://blog.csdn.net/qq_33897084/article/details/147341458 一、单选题(共25题&#xff0c;共50分) 1. 在tkinter的…...

OpenVINO怎么用

目录 OpenVINO 简介 主要组件 安装 OpenVINO 使用 OpenVINO 的基本步骤 OpenVINO 简介 OpenVINO&#xff08;Open Visual Inference and Neural Network Optimization&#xff09;是英特尔推出的一个开源工具包&#xff0c;旨在帮助开发者在英特尔硬件平台上高效部署深度学…...

欧拉服务器操作系统安装MySQL

1. 安装MySQL服务器​​ 1. 更新仓库缓存 sudo dnf makecache2. 安装MySQL sudo dnf install mysql-server2. 初始化数据库​ sudo mysqld --initialize --usermysql3. 启动数据库服务 # 启动服务 sudo systemctl start mysqld# 设置开机自启 sudo systemctl enable mysql…...

【零基础】基于 MATLAB + Gurobi + YALMIP 的优化建模与求解全流程指南

MATLAB Gurobi YALMIP 综合优化教程&#xff08;进阶&#xff09; 本教程系统介绍如何在 MATLAB 环境中使用 YALMIP 建模&#xff0c;并通过 Gurobi 求解器高效求解线性、整数及非线性优化问题。适用于工程、运营研究、能源系统等领域的高级优化建模需求。 一、工具概览 1.…...

Python 浮点数运算之谜:深入解析round(0.675, 2)等输出异常

一、问题背景&#xff1a;当浮点数运算遇见 “反直觉” 结果 在 Python 开发中&#xff0c;以下代码输出常让开发者困惑&#xff1a; print(round(0.675, 2)) # 预期0.67&#xff0c;实际0.68||预期0.68&#xff0c;实际0.67 print(0.1 0.2) # 预期0.3&…...

【C#】Html转Pdf,Spire和iTextSharp结合,.net framework 4.8

&#x1f339;欢迎来到《小5讲堂》&#x1f339; &#x1f339;这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。&#x1f339; &#x1f339;温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01;&#…...

极狐GitLab 注册限制如何设置?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 注册限制 (BASIC SELF) 您可以对注册实施以下限制&#xff1a; 禁用新注册。新注册需要管理员批准。需要用户电子邮件确认。…...

利用大模型实现地理领域文档中英文自动化翻译

一、 背景描述 在跨国性企业日常经营过程中&#xff0c;经常会遇到专业性较强的文档翻译的需求&#xff0c;例如法律文书、商务合同、技术文档等&#xff1b;以往遇到此类场景&#xff0c;企业内部往往需要指派专人投入数小时甚至数天来整理和翻译&#xff0c;效率低下&#x…...

SGFormer:卫星-地面融合 3D 语义场景补全

论文介绍 题目&#xff1a;SGFormer: Satellite-Ground Fusion for 3D Semantic Scene Completion 会议&#xff1a;IEEE / CVF Computer Vision and Pattern Recognition Conference 论文&#xff1a;https://www.arxiv.org/abs/2503.16825 代码&#xff1a;https://githu…...

Trinity三位一体开源程序是可解释的 AI 分析工具和 3D 可视化

一、软件介绍 文末提供源码和程序下载学习 Trinity三位一体开源程序是可解释的 AI 分析工具和 3D 可视化。Trinity 提供性能分析和 XAI 工具&#xff0c;非常适合深度学习系统或其他执行复杂分类或解码的模型。 二、软件作用和特征 Trinity 通过结合具有超维感知能力的不同交…...

城市街拍暗色电影胶片风格Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色介绍 城市街拍暗色电影胶片风格 Lr 调色&#xff0c;是借助 Adobe Lightroom 软件&#xff0c;为城市街拍的人像或场景照片赋予独特视觉风格的后期处理方式。旨在模拟电影胶片质感&#xff0c;营造出充满故事感与艺术感的暗色氛围&#xff0c;让照片仿佛截取于某部充满张力…...

【家政平台开发(55)】家政平台数据生命线:备份与恢复策略全解析

本【家政平台开发】专栏聚焦家政平台从 0 到 1 的全流程打造。从前期需求分析,剖析家政行业现状、挖掘用户需求与梳理功能要点,到系统设计阶段的架构选型、数据库构建,再到开发阶段各模块逐一实现。涵盖移动与 PC 端设计、接口开发及性能优化,测试阶段多维度保障平台质量,…...

加密和解密(大语言模型)

看到很多对matlab的p文件加密方案感兴趣的。网络上技术资料比较少&#xff0c;所以&#xff0c;我让大语言模型提供一些概论性质的东西&#xff0c;转发出来自娱自乐。期望了解p文件加密的复杂度&#xff0c;而不是一定要尝试挑战加密算法。 但根据大语言模型提供的材料&#…...

双轮驱动能源革命:能源互联网与分布式能源赋能工厂能效跃迁

在全球能源结构深度转型与“双碳”目标的双重驱动下&#xff0c;工厂作为能源消耗的主力军&#xff0c;正站在节能变革的关键节点。能源互联网与分布式能源技术的融合发展&#xff0c;为工厂节能开辟了全新路径。塔能科技凭借前沿技术与创新实践&#xff0c;深度探索能源协同优…...

React 更新 state 中的数组

更新 state 中的数组 数组是另外一种可以存储在 state 中的 JavaScript 对象&#xff0c;它虽然是可变的&#xff0c;但是却应该被视为不可变。同对象一样&#xff0c;当你想要更新存储于 state 中的数组时&#xff0c;你需要创建一个新的数组&#xff08;或者创建一份已有数组…...

ubantu18.04HDFS编程实践(Hadoop3.1.3)

说明&#xff1a;本文图片较多&#xff0c;耐心等待加载。&#xff08;建议用电脑&#xff09; 注意所有打开的文件都要记得保存。 第一步&#xff1a;准备工作 本文是在之前Hadoop搭建完集群环境后继续进行的&#xff0c;因此需要读者完成我之前教程的所有操作。 第二步&am…...

Spring Boot资源耗尽问题排查与优化

Spring Boot服务运行一段时间后新请求无法处理的问题。服务没有挂掉&#xff0c;也没有异常日志。思考可能是一些资源耗尽或阻塞的问题。 思考分析 首先&#xff0c;资源耗尽可能涉及线程池、数据库连接、内存、文件句柄或网络连接等。常见的如线程池配置不当&#xff0c;导致…...

优化WAV音频文件

优化 WAV 音频文件通常涉及 减小文件体积、提升音质 或 适配特定用途&#xff08;如流媒体、广播等&#xff09;。以下是分场景的优化方法&#xff0c;涵盖工具和操作步骤&#xff1a; 一、减小文件体积&#xff08;无损/有损压缩&#xff09; 1. 无损压缩 转换格式&#xff1…...

string函数具体事例

输出所有字串出现的位置 输入两个字符串A和B&#xff0c;输出B在A中出现的位置 输入 两行 第一行是一个含有空格的字符串 第二行是要查询的字串 输出 字串的位置 样例输入 I love c c python 样例输出 -1 样例输入 I love c c c 样例输出 8 12 #include<iostream> #inclu…...

8.Rust+Axum 数据库集成实战:从 ORM 选型到用户管理系统开发

摘要 深入探讨 RustAxum 数据库集成&#xff0c;包括 ORM 选型及实践&#xff0c;助力用户管理系统开发。 一、引言 在现代 Web 应用开发中&#xff0c;数据库集成是至关重要的一环。Rust 凭借其高性能、内存安全等特性&#xff0c;与 Axum 这个轻量级且高效的 Web 框架结合…...

电脑 BIOS 操作指南(Computer BIOS Operation Guide)

电脑 BIOS 操作指南 电脑的BIOS界面&#xff08;应为“BIOS”&#xff09;是一个固件界面&#xff0c;允许用户配置电脑的硬件设置。 进入BIOS后&#xff0c;你可以进行多种设置&#xff0c;具体包括&#xff1a; 1.启动配置 启动顺序&#xff1a;设置从哪个设备启动&#x…...

MySQL快速入门篇---库的操作

目录 一、创建数据库 1.语法 2.示例 二、查看数据库 1.语法 三、字符集编码和校验&#xff08;排序&#xff09;规则 1.查看数据库支持的字符集编码 2.查看数据库支持的排序规则 3.查看系统默认字符集和排序规则 3.1.查看系统默认字符集 3.2.查看系统默认排序规则 ​…...

前端:uniapp中uni.pageScrollTo方法与元素的overflow-y:auto之间的关联

在uniapp中&#xff0c;uni.pageScrollTo方法与元素的overflow-y:auto属性之间存在以下关联和差异&#xff1a; 一、功能定位差异 ‌uni.pageScrollTo‌ 属于‌页面级滚动控制‌&#xff0c;作用于整个页面容器‌34。要求页面内容高度必须超过屏幕高度&#xff0c;且由根元素下…...

【已更新完毕】2025华中杯B题数学建模网络挑战赛思路代码文章教学:校园共享单车的调度与维护问题

完整内容请看文末最后的推广群 构建校园共享单车的调度与维护问题 摘要 共享单车作为一种便捷、环保的短途出行工具&#xff0c;近年来在高校校园内得到了广泛应用。然而&#xff0c;共享单车的运营也面临一些挑战。某高校引入共享单车后&#xff0c;委托学生对运营情况进行调…...