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

发布于:2025-04-21 ⋅ 阅读:(66) ⋅ 点赞:(0)

      

目录

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

一、循环链表节点类

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

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

(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每次往后移动一个节点,然后将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}");
            }
        }
    }
}