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

news2025/4/19 11:35:34

      

目录

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

一、循环链表节点类

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

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

(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}");
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2337943.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity使用Newtonsoft.Json本地化存档

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

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的…

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;让照片仿佛截取于某部充满张力…

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

看到很多对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…

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

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

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

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

NO.92十六届蓝桥杯备战|图论基础-最小生成树-Prim算法-Kruskal算法|买礼物|繁忙的都市|滑雪(C++)

一个具有n个顶点的连通图&#xff0c;其⽣成树为包含n-1条边和所有顶点的极⼩连通⼦图。对于⽣成树来说&#xff0c;若砍去⼀条边就会使图不连通图&#xff1b;若增加⼀条边就会形成回路。 ⼀个图的⽣成树可能有多个&#xff0c;将所有⽣成树中权值之和最⼩的树称为最⼩⽣成树…

第十四节:实战场景-何实现全局状态管理?

React.createElement调用示例 Babel插件对JSX的转换逻辑 React 全局状态管理实战与 JSX 转换原理深度解析 一、React 全局状态管理实现方案 1. Context API useReducer 方案&#xff08;轻量级首选&#xff09; // 创建全局 Context 对象 const GlobalContext createConte…

数据驱动、精准协同:高端装备制造业三位一体生产管控体系构建

开篇引入 鉴于集团全面推行生产运营体建设以及对二级单位生产过程管控力度逐步加强&#xff0c;某高端装备制造企业生产部长王总正在开展新的一年企业生产管控规划工作&#xff0c;为了能够更好地进行体系规划与建设应用&#xff0c;特邀请智能制造专家小智来进行讨论交流。 王…

航电系统之通信技术篇

航电系统&#xff08;航空电子系统&#xff09;的通信技术是现代航空器的核心技术之一&#xff0c;其核心目标是实现飞行器内部各系统之间以及飞行器与外部设备&#xff08;如地面控制中心、其他飞行器等&#xff09;之间高效、可靠的信息交互。随着航空技术的不断发展&#xf…

发现“横”字手写有难度,对比两个“横”字

我发现手写体“横”字“好看”程度&#xff0c;难以比得上印刷体&#xff1a; 两个从方正简体启体来的“横”字&#xff1a; 哪个更好看&#xff1f;我是倾向于左边一点。 <div style"transform: rotate(180deg); display: inline-block;"> 左边是我从方正简…