??????
目錄
📊 鏈表三劍客:特性全景對比表
一、循環鏈表節點類
二、循環鏈表的整體設計框架
三、循環列表中的重要方法:
(1)頭插法,在頭結點前面插入新的節點
(2)尾插法實現插入元素:
?(3)刪除頭結點:
(4)刪除第一個指定數據的節點
?(5)檢查鏈表中是否存在指定數據這個就簡單了,直接遍歷,找到了就返回true沒找到就返回false
(6) 更新節點值
(7)實現一個迭代器,方便遍歷鏈表元素
(8)在指定索引插入值?
四、測試
五、總結
循環鏈表核心解析
1. 結構特性
2. 操作邏輯與實現要點
節點插入
節點刪除
3. 復雜度與性能
4. 應用場景
5. 邊界處理與易錯點
6. 對比與選型
7. 設計啟示
???
????????前面我們已經會晤了單向鏈表與雙向鏈表,今天我們來會會循環鏈表,其實循環鏈表就是將單向鏈表的尾指針指向了頭結點,那么如何保證不死循環呢,我們一起來看看吧。
????????循環鏈表是一種特殊的鏈表結構,其尾節點的指針不再指向null,而是指向頭節點形成閉環:
-
單向循環鏈表:尾節點.next = 頭節點
-
雙向循環鏈表:尾節點.next = 頭節點,頭節點.prev = 尾節點
? ? ? ? 如果關于循環鏈表還有不了解的讀者可以先去看下這篇文章:
? ? ? ?線性表的說明
三種鏈表的對比:
📊 鏈表三劍客:特性全景對比表
對比維度 | 單向鏈表 | 雙向鏈表 | 循環鏈表 |
---|---|---|---|
結構示意圖 | A → B → C → null | ←A ? B ? C→ | A → B → C → [HEAD] |
指針方向 | 單方向(Next) | 雙方向(Prev/Next) | 單/雙方向 + 閉環 |
頭節點訪問 | O(1) | O(1) | O(1) |
尾節點訪問 | O(n) | O(1)(維護尾指針時) | O(n)(可通過設計優化到O(1)) |
插入操作 | 頭插O(1),尾插O(n) | 頭尾插入均可O(1) | 頭尾插入均可O(n)(需維護閉環) |
刪除操作 | 需要前驅節點(平均O(n)) | 可直接刪除(O(1)) | 類似單向鏈表但需維護閉環 |
內存開銷 | 最低(每個節點1指針) | 較高(每個節點2指針) | 與單向相同,但需額外閉環指針 |
遍歷方向 | 單向 | 雙向 | 單向/雙向 + 循環 |
核心優勢 | 結構簡單,內存高效 | 快速反向遍歷,刪除高效 | 天然支持循環操作 |
經典應用場景 | 棧、簡單隊列 | LRU緩存、瀏覽器歷史記錄 | 輪詢調度、音樂循環播放 |
邊界處理復雜度 | 簡單(只需判斷null) | 中等(需處理雙向指針) | 較高(閉環維護易出錯) |
代碼示例特征 | while (current != null) | node.Prev.Next = node.Next | do {...} while (current != head) |
一、循環鏈表節點類
/// <summary>/// 循環鏈表節點類/// </summary>/// <typeparam name="T">節點數據類型</typeparam>public class CircularNode<T>{/// <summary>/// 節點存儲的數據/// </summary>public T Data { get; set; }/// <summary>/// 指向下一個節點的指針/// </summary>public CircularNode<T> Next { get; set; }/// <summary>/// 節點構造函數/// </summary>/// <param name="data">節點初始數據</param>/// <remarks>/// 初始化時將Next指向自身,形成最小閉環/// 當鏈表只有一個節點時,構成自環結構/// </remarks>public CircularNode(T data){Data = data;Next = this; // 關鍵閉環操作}}
二、循環鏈表的整體設計框架
/// <summary>
/// 單向循環鏈表實現類
/// </summary>
/// <typeparam name="T">鏈表元素類型</typeparam>
public class CircularLinkedList<T>
{/// <summary>/// 鏈表頭節點(關鍵指針)/// </summary>private CircularNode<T> head;/// <summary>/// 節點計數器(優化統計效率)/// </summary>private int count;/// <summary>/// 鏈表元素數量(O(1)時間復雜度)/// </summary>public int Count => count;/// <summary>/// 判斷鏈表是否為空/// </summary>public bool IsEmpty => head == null;/// <summary>/// 打印鏈表內容(調試用方法)/// </summary>public void PrintAll(){if (head == null){Console.WriteLine("[Empty List]");return;}var current = head;do{Console.Write($"{current.Data} -> ");current = current.Next;} while (current != head); // 循環終止條件判斷Console.WriteLine("[HEAD]"); // 閉環標記}
}
三、循環列表中的重要方法:
(1)頭插法,在頭結點前面插入新的節點
假設我們有這樣一個循環鏈表:
那么我們如何利用頭插法進行插入新節點呢:
? ? ? ? ①先創建一個新節點
? ? ? ? ②判斷當前鏈表是否為空,為空則將當前新節點置為頭結點
? ? ? ? ③當前鏈表存在,則,首先找到尾節點,然后將新節點指向原先的頭結點,接著將新節點覆蓋原先頭結點,最后將尾節點指向新的頭結點即可:如下圖所示
? ? ? ? ④計數器++
代碼實現:
/// <summary>
/// 在鏈表頭部插入新節點
/// </summary>
/// <param name="data">插入數據</param>
/// <remarks>
/// 時間復雜度:O(n)(需要遍歷找到尾節點)
/// 特殊情況處理:
/// 1. 空鏈表插入
/// 2. 單節點鏈表插入
/// 3. 多節點鏈表插入
/// </remarks>
public void AddFirst(T data)
{var newNode = new CircularNode<T>(data);if (head == null){// 空鏈表情況處理head = newNode;}else{// 查找當前尾節點(關鍵步驟)var tail = head;while (tail.Next != head){tail = tail.Next;}// 新節點指向原頭節點newNode.Next = head;// 更新頭指針head = newNode;// 更新尾節點指向新頭(維持閉環)tail.Next = head;}count++; // 更新計數器
}
(2)尾插法實現插入元素:
? ? ? ? 思想:
①創建一個新節點
②如果當前鏈表為空,則將當前頭結點設置為新節點,然后指向自己,閉環
③當前節點不為空,首先找到尾節點,接著將尾節點指向新節點,然后將新節點指向頭結點,完畢!
④計數器++
實現代碼:
/// <summary>
/// 在鏈表尾部插入新節點
/// </summary>
/// <param name="data">插入數據</param>
/// <remarks>
/// 時間復雜度:O(n)(需要遍歷到尾部)
/// 優化思路:可以維護尾指針變量將時間復雜度降為O(1)
/// </remarks>
public void AddLast(T data)
{var newNode = new CircularNode<T>(data);if (head == null){// 空鏈表處理head = newNode;head.Next = head; // 自環處理}else{// 查找當前尾節點var tail = head;while (tail.Next != head){tail = tail.Next;}// 新節點指向頭節點newNode.Next = head;// 當前尾節點指向新節點tail.Next = newNode;}count++;
}
?(3)刪除頭結點:
①:判斷列表是否有值,沒有刪除就是錯誤手段,應拋出錯誤
②:判斷是否只有一個節點,是的話直接置空
③:多節點時,先找到尾節點,將頭結點更新為頭結點的下一個,將尾節點指向新的頭結點
代碼實現:
④:計數器--
/// <summary>
/// 刪除鏈表頭節點
/// </summary>
/// <exception cref="InvalidOperationException">空鏈表刪除時拋出異常</exception>
/// <remarks>
/// 重點處理:
/// 1. 空鏈表異常
/// 2. 單節點鏈表刪除
/// 3. 多節點鏈表刪除
/// </remarks>
public void RemoveFirst()
{if (head == null)throw new InvalidOperationException("Cannot remove from empty list");if (head.Next == head) // 單節點判斷條件{// 清除頭節點引用head = null;}else{// 查找當前尾節點var tail = head;while (tail.Next != head){tail = tail.Next;}// 移動頭指針到下一節點head = head.Next;// 更新尾節點指向新頭tail.Next = head;}count--; // 更新計數器
}
(4)刪除第一個指定數據的節點
①:先判斷是否為空鏈表,為空直接拋出錯誤
②:然后準備兩個臨時節點,一個是current,一個是previous。還有一個標志位:found。
????????current用來記錄當前節點,在鏈表中一直跑跑跑,如果找到了目標值,就直接退出循環;還有就是當current的下一個節點是頭結點時,說明此時已經到了尾節點,如果此刻的值還不等于,說明就是沒找到。
????????previous是為了判斷當前節點是否為頭結點,那怎么判斷是不是頭結點呢,我們首先會將previous置空,current每次往后移動一個節點,然后將previous用current覆蓋,如果在經歷了current遍歷鏈表之后,previous還是空,?說明什么?說明目標值就是頭結點,那么此刻就是刪除頭結點的操作;
? ? ? ? 刪除操作:1)如果刪除的頭結點,要進行判斷是否是單節點,是的話直接置空,不是的話要找到尾節點,然后將重復上面的刪除頭結點操作
? ? ? ? ? ? ? ? ? ? ? ? ?2)刪除的不是頭結點,就直接將previous的下一個指向current的下一個就好,因為你的previous是當前目標節點的前一個,你想刪除當前節點,那么是不是就是將前一個節點的下一個指向當前目標節點的下一個,這樣自己就被刪除了。這里我們還要加一個特殊判斷,就是如果當前節點是歷史頭結點,那么需要更新這個頭結點
代碼如下:
/// <summary>
/// 刪除第一個匹配的節點
/// </summary>
/// <param name="data">要刪除的數據</param>
/// <returns>是否成功刪除</returns>
/// <remarks>
/// 關鍵點:
/// 1. 循環遍歷時的終止條件
/// 2. 頭節點刪除的特殊處理
/// 3. 單節點鏈表的處理
/// </remarks>
public bool Remove(T data)
{if (head == null) return false;CircularNode<T> current = head;CircularNode<T>? previous = null;bool found = false;// 使用do-while確保至少執行一次循環do{if (EqualityComparer<T>.Default.Equals(current.Data, data)){found = true;break;}previous = current;current = current.Next;} while (current != head);if (!found) return false;// 刪除節點邏輯if (previous == null) // 刪除的是頭節點{if (head.Next == head) // 唯一節點情況{head = null;}else{// 查找當前尾節點var tail = head;while (tail.Next != head){tail = tail.Next;}// 移動頭指針head = head.Next;// 更新尾節點指向新頭tail.Next = head;}}else // 刪除中間或尾部節點{previous.Next = current.Next;// 如果刪除的是原頭節點(current == head)if (current == head) // 防御性檢查{head = previous.Next; // 強制更新頭指針}}count--;return true;
}
?(5)檢查鏈表中是否存在指定數據
這個就簡單了,直接遍歷,找到了就返回true沒找到就返回false
代碼如下:
/// <summary>
/// 檢查鏈表中是否存在指定數據
/// </summary>
/// <param name="data">查找目標數據</param>
/// <returns>存在返回true</returns>
/// <remarks>
/// 使用值相等比較(EqualityComparer.Default)
/// 注意:對于引用類型需要正確實現Equals方法
/// </remarks>
public bool Contains(T data)
{if (head == null) return false;var current = head;do{if (EqualityComparer<T>.Default.Equals(current.Data, data)){return true;}current = current.Next;} while (current != head); // 完整遍歷一圈return false;
}
(6) 更新節點值
這個在上面查找的基礎上直接修改值就行了:
/// <summary>
/// 修改第一個匹配的節點值
/// </summary>
/// <param name="oldValue">舊值</param>
/// <param name="newValue">新值</param>
/// <returns>修改成功返回true</returns>
/// <remarks>
/// 注意:此方法直接修改節點數據引用
/// 如果節點存儲的是引用類型,需要注意副作用
/// </remarks>
public bool Update(T oldValue, T newValue)
{if (head == null) return false;var current = head;do{if (EqualityComparer<T>.Default.Equals(current.Data, oldValue)){current.Data = newValue; // 直接修改數據引用return true;}current = current.Next;} while (current != head);return false;
}
(7)實現一個迭代器,方便遍歷鏈表元素
/// <summary>
/// 實現迭代器
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{if (head == null) yield break;var current = head;do{yield return current.Data;current = current.Next;} while (current != head);
}
(8)在指定索引插入值?
①:先判斷索引值是否合理,不合理直接拋出錯誤
②:判斷是否在第一個位置插入,是的話,直接調用AddFirst();
③:判斷是否在最后一個位置插入,是的話直接調用AddLast();
④:for循環,找到索引位置的前一個,將當前節點的下一個節點值存起來,然后指向新結點,最后將新節點指向下一個節點
代碼如下:
⑤:計數器++
/// <summary>/// 在指定索引位置插入節點/// </summary>/// <param name="index">插入位置(0-based)</param>/// <param name="data">插入數據</param>/// <exception cref="ArgumentOutOfRangeException">索引越界時拋出</exception>/// <remarks>/// 索引有效性檢查:/// - index < 0 或 index > count 時拋出異常/// 當index=0時等價于AddFirst/// 當index=count時等價于AddLast/// </remarks>public void InsertAt(int index, T data){if (index < 0 || index > count)throw new ArgumentOutOfRangeException(nameof(index));if (index == 0){AddFirst(data);return;}if (index == count){AddLast(data);return;}var newNode = new CircularNode<T>(data);var current = head;// 移動到插入位置前驅節點for (int i = 0; i < index - 1; i++){current = current.Next;}// 插入新節點newNode.Next = current.Next;current.Next = newNode;count++;}
四、測試
internal class Program{static void Main(string[] args){// 初始化循環鏈表var playlist = new CircularLinkedList<string>();Console.WriteLine($"新建播放列表,是否為空:{playlist.IsEmpty}");// 添加歌曲(混合使用頭插和尾插)playlist.AddFirst("晴天 - 周杰倫");playlist.AddLast("七里香 - 周杰倫");playlist.AddFirst("夜曲 - 周杰倫");playlist.PrintAll(); // 輸出:夜曲 -> 晴天 -> 七里香 -> [HEAD]// 插入操作playlist.InsertAt(1, "稻香 - 周杰倫");Console.WriteLine("\n插入新歌曲后:");playlist.PrintAll(); // 輸出:夜曲 -> 稻香 -> 晴天 -> 七里香 -> [HEAD]// 刪除操作playlist.RemoveFirst();Console.WriteLine("\n刪除首曲后:");playlist.PrintAll(); // 輸出:稻香 -> 晴天 -> 七里香 -> [HEAD]bool removed = playlist.Remove("晴天 - 周杰倫");Console.WriteLine($"\n刪除晴天結果:{removed}");playlist.PrintAll(); // 輸出:稻香 -> 七里香 -> [HEAD]// 查找測試bool exists = playlist.Contains("七里香 - 周杰倫");Console.WriteLine($"\n是否包含七里香:{exists}"); // 輸出:True// 更新操作bool updated = playlist.Update("稻香 - 周杰倫", "稻香(Remix版) - 周杰倫");Console.WriteLine($"\n更新稻香結果:{updated}");playlist.PrintAll(); // 輸出:稻香(Remix版) -> 七里香 -> [HEAD]// 邊界測試:刪除最后一個節點playlist.Remove("七里香 - 周杰倫");Console.WriteLine("\n刪除七里香后:");playlist.PrintAll(); // 輸出:稻香(Remix版) -> [HEAD]// 異常處理測試try{var emptyList = new CircularLinkedList<int>();emptyList.RemoveFirst(); // 觸發異常}catch (InvalidOperationException ex){Console.WriteLine($"\n異常捕獲:{ex.Message}");}// 使用迭代器遍歷Console.WriteLine("\n當前播放列表循環播放:");foreach (var song in playlist){Console.WriteLine($"正在播放:{song}");}}}
測試結果:
?
五、總結
循環鏈表核心解析
1. 結構特性
循環鏈表是一種首尾相連的鏈式結構,其核心特征為:
- ?閉環設計?:尾節點的指針不再指向空值,而是指向頭節點,形成環形鏈路。
- ?自洽節點?:每個新節點創建時默認指向自身,即使鏈表僅有一個節點也能維持閉合回路。
- ?遍歷特性?:從任意節點出發均可遍歷整個鏈表,沒有傳統鏈表的“終點”概念。
2. 操作邏輯與實現要點
節點插入
-
?頭插法?
新節點成為鏈表的起點:- 若鏈表為空,新節點自環即為頭節點。
- 若鏈表非空,需先找到尾節點(遍歷至?
Next
?指向頭節點的節點)。 - 新節點的?
Next
?指向原頭節點,尾節點的?Next
?更新為新節點,頭指針重置為新節點。
?耗時?:O(n)(查找尾節點),維護尾指針可優化至 O(1)。
-
?尾插法?
新節點成為鏈表的終點:- 若鏈表為空,處理邏輯同頭插法。
- 若鏈表非空,遍歷找到尾節點后,使其?
Next
?指向新節點,新節點的?Next
?指向頭節點。
?耗時?:O(n),維護尾指針可優化。
節點刪除
-
?刪除頭節點?
- 單節點鏈表:直接置空頭指針。
- 多節點鏈表:查找尾節點,將其?
Next
?指向原頭節點的下一節點,更新頭指針。
?關鍵?:確保尾節點與新頭節點的連接,避免閉環斷裂。
-
?刪除指定節點?
- 遍歷鏈表匹配目標值,記錄前驅節點。
- 若目標為頭節點:按頭節點刪除邏輯處理。
- 若為中間節點:前驅節點的?
Next
?跳過目標節點,直接指向其后繼節點。
?注意?:刪除后需校驗頭指針是否失效,防止邏輯錯誤。
3. 復雜度與性能
-
?時間復雜度?
- 基礎操作(頭插、尾插、刪除):默認 O(n),因需查找尾節點或遍歷匹配。
- 優化策略:維護尾指針變量,可將頭尾操作降至 O(1)。
- 查詢與修改:O(n),需遍歷至目標位置。
-
?空間復雜度?
與單向鏈表一致,每個節點僅需存儲數據和單個指針,無額外內存負擔。
4. 應用場景
-
?循環任務調度?
如操作系統的輪詢機制,循環鏈表可自然支持任務隊列的循環執行。 -
?多媒體播放控制?
音樂播放器的“循環播放”模式,通過鏈表閉環實現歌曲無縫銜接。 -
?游戲邏輯設計?
多玩家回合制游戲中,循環鏈表可管理玩家順序,實現循環回合。 -
?資源池管理?
數據庫連接池等場景,循環分配資源時可通過鏈表快速定位下一個可用資源。
5. 邊界處理與易錯點
-
?空鏈表操作?
插入首個節點時需維護自環,刪除操作前必須檢查鏈表是否為空,避免空指針異常。 -
?單節點維護?
刪除僅有的節點后,需及時置空頭指針,防止遺留無效引用。 -
?循環終止條件?
遍歷時使用?do-while
?結構,確保至少訪問頭節點一次,終止條件為回到起始點。 -
?閉環完整性?
任何操作后需驗證尾節點的?Next
?是否指向頭節點,防止閉環斷裂導致死循環。
6. 對比與選型
-
?VS 單向鏈表?
- 優勢:天然支持循環訪問,尾節點操作更易優化。
- 劣勢:刪除非頭節點時仍需遍歷,代碼復雜度稍高。
-
?VS 雙向鏈表?
- 優勢:內存占用更低,適合單向循環足夠使用的場景。
- 劣勢:無法快速反向遍歷,中間節點刪除效率較低。
7. 設計啟示
-
?擴展性考量?
可增加?Tail
?指針變量,將尾節點訪問從 O(n) 優化至 O(1),提升高頻尾插場景性能。 -
?迭代器安全?
實現自定義迭代器時,需處理鏈表在遍歷過程中被修改的情況,避免并發沖突。 -
?數據一致性?
節點刪除后應及時更新計數器(count
),確保?Count
?屬性準確反映實際長度。
????????好的呀。我們終于結束了關于鏈表的知識了!繼續前進!
附本文所有代碼:
namespace 循環鏈表
{/// <summary>/// 循環鏈表節點類/// </summary>/// <typeparam name="T">節點數據類型</typeparam>public class CircularNode<T>{/// <summary>/// 節點存儲的數據/// </summary>public T Data { get; set; }/// <summary>/// 指向下一個節點的指針/// </summary>public CircularNode<T> Next { get; set; }/// <summary>/// 節點構造函數/// </summary>/// <param name="data">節點初始數據</param>/// <remarks>/// 初始化時將Next指向自身,形成最小閉環/// 當鏈表只有一個節點時,構成自環結構/// </remarks>public CircularNode(T data){Data = data;Next = this; // 關鍵閉環操作}}/// <summary>/// 單向循環鏈表實現類/// </summary>/// <typeparam name="T">鏈表元素類型</typeparam>public class CircularLinkedList<T>{/// <summary>/// 鏈表頭節點(關鍵指針)/// </summary>private CircularNode<T>? head;/// <summary>/// 節點計數器(優化統計效率)/// </summary>private int count;/// <summary>/// 鏈表元素數量(O(1)時間復雜度)/// </summary>public int Count => count;/// <summary>/// 判斷鏈表是否為空/// </summary>public bool IsEmpty => head == null;/// <summary>/// 打印鏈表內容(調試用方法)/// </summary>public void PrintAll(){if (head == null){Console.WriteLine("[Empty List]");return;}var current = head;do{Console.Write($"{current.Data} -> ");current = current.Next;} while (current != head); // 循環終止條件判斷Console.WriteLine("[HEAD]"); // 閉環標記}/// <summary>/// 在鏈表頭部插入新節點/// </summary>/// <param name="data">插入數據</param>/// <remarks>/// 時間復雜度:O(n)(需要遍歷找到尾節點)/// 特殊情況處理:/// 1. 空鏈表插入/// 2. 單節點鏈表插入/// 3. 多節點鏈表插入/// </remarks>public void AddFirst(T data){var newNode = new CircularNode<T>(data);if (head == null){// 空鏈表情況處理head = newNode;}else{// 查找當前尾節點(關鍵步驟)var tail = head;while (tail.Next != head){tail = tail.Next;}// 新節點指向原頭節點newNode.Next = head;// 更新頭指針head = newNode;// 更新尾節點指向新頭(維持閉環)tail.Next = head;}count++; // 更新計數器}/// <summary>/// 在鏈表尾部插入新節點/// </summary>/// <param name="data">插入數據</param>/// <remarks>/// 時間復雜度:O(n)(需要遍歷到尾部)/// 優化思路:可以維護尾指針變量將時間復雜度降為O(1)/// </remarks>public void AddLast(T data){var newNode = new CircularNode<T>(data);if (head == null){// 空鏈表處理head = newNode;head.Next = head; // 自環處理}else{// 查找當前尾節點var tail = head;while (tail.Next != head){tail = tail.Next;}// 新節點指向頭節點newNode.Next = head;// 當前尾節點指向新節點tail.Next = newNode;}count++;}/// <summary>/// 刪除鏈表頭節點/// </summary>/// <exception cref="InvalidOperationException">空鏈表刪除時拋出異常</exception>/// <remarks>/// 重點處理:/// 1. 空鏈表異常/// 2. 單節點鏈表刪除/// 3. 多節點鏈表刪除/// </remarks>public void RemoveFirst(){if (head == null)throw new InvalidOperationException("Cannot remove from empty list");if (head.Next == head) // 單節點判斷條件{// 清除頭節點引用head = null;}else{// 查找當前尾節點var tail = head;while (tail.Next != head){tail = tail.Next;}// 移動頭指針到下一節點head = head.Next;// 更新尾節點指向新頭tail.Next = head;}count--; // 更新計數器}/// <summary>/// 刪除第一個匹配的節點/// </summary>/// <param name="data">要刪除的數據</param>/// <returns>是否成功刪除</returns>/// <remarks>/// 關鍵點:/// 1. 循環遍歷時的終止條件/// 2. 頭節點刪除的特殊處理/// 3. 單節點鏈表的處理/// </remarks>public bool Remove(T data){if (head == null) return false;CircularNode<T> current = head;CircularNode<T>? previous = null;bool found = false;// 使用do-while確保至少執行一次循環do{if (EqualityComparer<T>.Default.Equals(current.Data, data)){found = true;break;}previous = current;current = current.Next;} while (current != head);if (!found) return false;// 刪除節點邏輯if (previous == null) // 刪除的是頭節點{if (head.Next == head) // 唯一節點情況{head = null;}else{// 查找當前尾節點var tail = head;while (tail.Next != head){tail = tail.Next;}// 移動頭指針head = head.Next;// 更新尾節點指向新頭tail.Next = head;}}else // 刪除中間或尾部節點{previous.Next = current.Next;// 如果刪除的是原頭節點(current == head)if (current == head){head = previous.Next; // 更新頭指針}}count--;return true;}/// <summary>/// 檢查鏈表中是否存在指定數據/// </summary>/// <param name="data">查找目標數據</param>/// <returns>存在返回true</returns>/// <remarks>/// 使用值相等比較(EqualityComparer.Default)/// 注意:對于引用類型需要正確實現Equals方法/// </remarks>public bool Contains(T data){if (head == null) return false;var current = head;do{if (EqualityComparer<T>.Default.Equals(current.Data, data)){return true;}current = current.Next;} while (current != head); // 完整遍歷一圈return false;}/// <summary>/// 修改第一個匹配的節點值/// </summary>/// <param name="oldValue">舊值</param>/// <param name="newValue">新值</param>/// <returns>修改成功返回true</returns>/// <remarks>/// 注意:此方法直接修改節點數據引用/// 如果節點存儲的是引用類型,需要注意副作用/// </remarks>public bool Update(T oldValue, T newValue){if (head == null) return false;var current = head;do{if (EqualityComparer<T>.Default.Equals(current.Data, oldValue)){current.Data = newValue; // 直接修改數據引用return true;}current = current.Next;} while (current != head);return false;}/// <summary>/// 在指定索引位置插入節點/// </summary>/// <param name="index">插入位置(0-based)</param>/// <param name="data">插入數據</param>/// <exception cref="ArgumentOutOfRangeException">索引越界時拋出</exception>/// <remarks>/// 索引有效性檢查:/// - index < 0 或 index > count 時拋出異常/// 當index=0時等價于AddFirst/// 當index=count時等價于AddLast/// </remarks>public void InsertAt(int index, T data){if (index < 0 || index > count)throw new ArgumentOutOfRangeException(nameof(index));if (index == 0){AddFirst(data);return;}if (index == count){AddLast(data);return;}var newNode = new CircularNode<T>(data);var current = head;// 移動到插入位置前驅節點for (int i = 0; i < index - 1; i++){current = current.Next;}// 插入新節點newNode.Next = current.Next;current.Next = newNode;count++;}/// <summary>/// 實現迭代器/// </summary>/// <returns></returns>public IEnumerator<T> GetEnumerator(){if (head == null) yield break;var current = head;do{yield return current.Data;current = current.Next;} while (current != head);}}internal class Program{static void Main(string[] args){// 初始化循環鏈表var playlist = new CircularLinkedList<string>();Console.WriteLine($"新建播放列表,是否為空:{playlist.IsEmpty}");// 添加歌曲(混合使用頭插和尾插)playlist.AddFirst("晴天 - 周杰倫");playlist.AddLast("七里香 - 周杰倫");playlist.AddFirst("夜曲 - 周杰倫");playlist.PrintAll(); // 輸出:夜曲 -> 晴天 -> 七里香 -> [HEAD]// 插入操作playlist.InsertAt(1, "稻香 - 周杰倫");Console.WriteLine("\n插入新歌曲后:");playlist.PrintAll(); // 輸出:夜曲 -> 稻香 -> 晴天 -> 七里香 -> [HEAD]// 刪除操作playlist.RemoveFirst();Console.WriteLine("\n刪除首曲后:");playlist.PrintAll(); // 輸出:稻香 -> 晴天 -> 七里香 -> [HEAD]bool removed = playlist.Remove("晴天 - 周杰倫");Console.WriteLine($"\n刪除晴天結果:{removed}");playlist.PrintAll(); // 輸出:稻香 -> 七里香 -> [HEAD]// 查找測試bool exists = playlist.Contains("七里香 - 周杰倫");Console.WriteLine($"\n是否包含七里香:{exists}"); // 輸出:True// 更新操作bool updated = playlist.Update("稻香 - 周杰倫", "稻香(Remix版) - 周杰倫");Console.WriteLine($"\n更新稻香結果:{updated}");playlist.PrintAll(); // 輸出:稻香(Remix版) -> 七里香 -> [HEAD]// 邊界測試:刪除最后一個節點playlist.Remove("七里香 - 周杰倫");Console.WriteLine("\n刪除七里香后:");playlist.PrintAll(); // 輸出:稻香(Remix版) -> [HEAD]// 異常處理測試try{var emptyList = new CircularLinkedList<int>();emptyList.RemoveFirst(); // 觸發異常}catch (InvalidOperationException ex){Console.WriteLine($"\n異常捕獲:{ex.Message}");}// 使用迭代器遍歷Console.WriteLine("\n當前播放列表循環播放:");foreach (var song in playlist){Console.WriteLine($"正在播放:{song}");}}}
}