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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/76484.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/76484.shtml
英文地址,請注明出處:http://en.pswp.cn/web/76484.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

交換網絡基礎

學習目標 掌握交換機的基本工作原理 掌握交換機的基本配置 交換機的基本工作原理 交換機是局域網&#xff08;LAN&#xff09;中實現數據高效轉發的核心設備&#xff0c;工作在 數據鏈路層&#xff08;OSI 模型第二層&#xff09;&#xff0c;其基本工作原理可概括為 “學習…

科學研究:怎么做

科研&#xff08;科學研究&#xff09;?? 是指通過系統化的方法&#xff0c;探索自然、社會或人文領域的未知問題&#xff0c;以發現新知識、驗證理論或解決實際問題的活動。它的核心是??基于證據的探索與創新??&#xff0c;旨在推動人類認知和技術的進步。 科研的核心要…

算法題(128):費解的開關

審題&#xff1a; 本題需要我們將多組測試用例中拉燈數小于等于6的最小拉燈數輸出&#xff0c;若拉燈數最小值仍大于6&#xff0c;則輸出-1 思路&#xff1a; 方法一&#xff1a;二進制枚舉 首先我們先分析一下基本特性&#xff1a; 1.所有的燈不可能重復拉&#xff1a;若拉的數…

MFC文件-屏幕錄像

下載本文件 本文件將獲取屏幕圖像數據的所有代碼整合到兩個文件中&#xff08;ScreenRecorder.h和ScreenRecorder.cpp&#xff09;&#xff0c;使獲取屏幕圖像數據變得簡單。輸出IYUV視頻流。還可以獲取系統播放的聲音&#xff0c;輸出PCM音頻流。由于使用了MFC類&#xff0c;本…

0801ajax_mock-網絡ajax請求1-react-仿低代碼平臺項目

0 vite配置proxy代理 vite.config.ts代碼如下圖所示&#xff1a; import { defineConfig } from "vite"; import react from "vitejs/plugin-react";// https://vite.dev/config/ export default defineConfig({plugins: [react()],server: {proxy: {&qu…

JVM筆記【一】java和Tomcat類加載機制

JVM筆記一java和Tomcat類加載機制 java和Tomcat類加載機制 Java類加載 * loadClass加載步驟類加載機制類加載器初始化過程雙親委派機制全盤負責委托機制類關系圖自定義類加載器打破雙親委派機制 Tomcat類加載器 * 為了解決以上問題&#xff0c;tomcat是如何實現類加載機制的…

IP編址(來自YESLAB新網工的筆記)

上層協議類型 概念&#xff1a;通常指的是位于網絡層&#xff08;如 IP 層&#xff09;以上的協議類型&#xff0c;這些協議在數據傳輸時需要由網絡層&#xff08;或更低層&#xff09;協議承載。以 IP 協議為例&#xff0c;IP 報文頭部中的 協議字段&#xff08;Protocol Fie…

SpringBoot學習(過濾器Filter。攔截器Interceptor。全局異常捕獲處理器GlobalExceptionHandler)(詳細使用教程)

目錄 一、過濾器Filter。 1.1定義與規范。 1.2工作原理與范圍。 1.3使用場景。 1.4 SpringBoot實現過濾器。&#xff08;Filter配置2種方式&#xff09; <1>注解配置(WebFilter、Order、ServletComponentScan)。 創建過濾器類。 啟用 Servlet 組件掃描。 <2>配置類…

c++題目_P1443 馬的遍歷

P1443 馬的遍歷 # P1443 馬的遍歷 ## 題目描述 有一個 $n \times m$ 的棋盤&#xff0c;在某個點 $(x, y)$ 上有一個馬&#xff0c;要求你計算出馬到達棋盤上任意一個點最少要走幾步。 ## 輸入格式 輸入只有一行四個整數&#xff0c;分別為 $n, m, x, y$。 ## 輸出格式 …

清華《數據挖掘算法與應用》K-means聚類算法

使用k均值聚類算法對表4.1中的數據進行聚類。代碼參考P281。 創建一個名為 testSet.txt 的文本文件&#xff0c;將以下內容復制粘貼進去保存即可&#xff1a; 0 0 1 2 3 1 8 8 9 10 10 7 表4.1 # -*- coding: utf-8 -*- """ Created on Thu Apr 17 16:59:58 …

HarmonyOS-ArkUI V2工具類:AppStorageV2:應用全局UI狀態存儲

AppStorageV2是一個能夠跨界面存儲數據,管理數據的類。開發者可以使用AppStorageV2來存儲全局UI狀態變量數據。它提供的是應用級的全局共享能力,開發者可以通過connect綁定同一個key,進行跨ability數據共享。 概述 AppStorageV2是一個單例,創建時間是應用UI啟動時。其目的…

打靶日記 zico2: 1

一、探測靶機IP&#xff08;進行信息收集&#xff09; 主機發現 arp-scan -lnmap -sS -sV -T5 -p- 192.168.10.20 -A二、進行目錄枚舉 發現dbadmin目錄下有個test_db.php 進入后發現是一個登錄界面&#xff0c;嘗試弱口令&#xff0c;結果是admin&#xff0c;一試就出 得到加…

使用Java基于Geotools的SLD文件編程式創建與磁盤生成實戰

前言 在地理信息系統&#xff08;GIS&#xff09;領域&#xff0c;地圖的可視化呈現至關重要&#xff0c;而樣式定義語言&#xff08;SLD&#xff09;文件為地圖元素的樣式配置提供了強大的支持。SLD 能夠精確地定義地圖圖層中各類要素&#xff08;如點、線、面、文本等&#x…

kubernetes》》k8s》》Service

Kubernetes 中的 Service 是用于暴露應用服務的核心抽象&#xff0c;為 Pod 提供穩定的訪問入口、負載均衡和服務發現機制。Service在Kubernetes中代表了一組Pod的邏輯集合&#xff0c;通過創建一個Service&#xff0c;可以為一組具有相同功能的容器應用提供一個統一的入口地址…

【HDFS】EC重構過程中的校驗功能:DecodingValidator

一、動機 DecodingValidator是在HDFS-15759中引入的一個用于校驗EC數據重構正確性的組件。 先說下引入DecodingValidator的動機,據很多已知的ISSUE(如HDFS-14768, HDFS-15186, HDFS-15240,這些目前都已經fix了)反饋, EC在重構的時候可能會有各種各樣的問題,導致數據錯誤…

現代c++獲取linux系統架構

現代c獲取linux系統架構 前言一、使用命令獲取系統架構二、使用c代碼獲取系統架構三、驗證四、總結 前言 本文介紹一種使用c獲取linux系統架構的方法。 一、使用命令獲取系統架構 linux系統中可以使用arch或者uname -m命令來獲取當前系統架構&#xff0c;如下圖所示 archuna…

didFinishLaunching 與「主線程首次 idle」, 哪個是更優的啟動結束時間點 ?

結論先行 在這兩個候選時間點里—— application:didFinishLaunchingWithOptions: 執行結束主線程第一次進入 idle&#xff08;RunLoop kCFRunLoopBeforeWaiting&#xff09; 若你只能二選一&#xff0c;以「主線程首次 idle」作為 啟動結束 更合理。它比 didFinishLaunchin…

Vue3 + TypeScript中defineEmits 類型定義解析

TypeScript 中 Vue 3 的 defineEmits 函數的類型定義&#xff0c;用于聲明組件可以觸發的事件。以下是分步解釋&#xff1a; 1. 泛型定義 ts <"closeDialog" | "getApplySampleAndItemX"> 作用&#xff1a;定義允許的事件名稱集合&#xff0c;即組…

樹莓派超全系列教程文檔--(34)樹莓派配置GPIO

配置GPIO GPIO控制gpio 文章來源&#xff1a; http://raspberry.dns8844.cn/documentation 原文網址 GPIO控制 gpio 通過 gpio 指令&#xff0c;可以在啟動時將 GPIO 引腳設置為特定模式和值&#xff0c;而以前需要自定義 dt-blob.bin 文件。每一行都對一組引腳應用相同的設…

AladdinEdu(H卡GPU算力平臺)使用教程: 1)注冊與開通流程 2)插件使用流程

一、注冊與開通流程 首先進入AladdinEdu官網&#xff1a;AladdinEdu-同學們用得起的H卡算力平臺-高效做AI就上Aladdin 完成注冊&#xff0c;并進行學生認證&#xff1a;學生認證賬戶&#xff0c;認證期間享受教育優惠價。 登錄官網進入控制臺 二、插件使用流程 VScode中…