1、簡介
鏈表是一種非常基礎的數據結構之一,我們在日常開發種都會接觸到或者是接觸到相同類型的鏈表數據結構.所以本文會使用C#算法來實現一個簡單的鏈表數據結構,並實現其中幾個簡單的api以供使用.
2、概述
鏈表是一種遞歸的數據結構,他或者為null,或者是指向像一個節點的(node)的引用,該節點含有一個泛型的元素(當然可以是非泛型的,但是為了充分利用C#的優勢,切讓鏈表更具有靈活性,這里使用泛型)和指向另一個鏈表的引用.
3、實戰 單向鏈表
如下圖,因為下一個節點對象沒有保持上個節點的引用,所以這種鏈表稱之為單向鏈表
實現代碼如下,這邊我使用了迭代器模式,方便節點的單向遍歷,因為沒有使用MS提供的標准的迭代器接口,所以無法使用foreach遍歷.
/// <summary> /// C#鏈表實現 /// </summary> public class LinkedList { static void Main(string[] args) { //生成對應的Node節點 var nodeFirst = new Node<string>(); var nodeSecond = new Node<string>(); var nodeThird = new Node<string>(); //構造節點內容 nodeFirst.Item = "one"; nodeSecond.Item = "two"; nodeThird.Item = "three"; //鏈接節點 nodeFirst.NodeItem = nodeSecond; nodeSecond.NodeItem = nodeThird; //注:這里nodeThird的NodeItem指向null var nodeEnumerator = nodeFirst.GetEnumerator(); while (nodeEnumerator.MoveNext()) { Console.Write($"當前節點元素內容:{nodeEnumerator.CurrentItem}"); //這里如果當前節點的下一個節點不為空,則讓當前節點變為下一個節點 if (nodeEnumerator.SetNext()) Console.WriteLine($"下一個節點內容:{nodeEnumerator.CurrentNode.Item}"); else Console.WriteLine($"鏈表遍歷結束,下一個節點內容為null"); } Console.ReadKey(); } /// <summary> /// 節點對象,使用迭代器模式,實現鏈表的遍歷 /// </summary> /// <typeparam name="T"></typeparam> public class Node<T> : ILinkedListEnumerable<T> { public T Item { get; set; } public Node<T> NodeItem { get; set; } public ILinedListEnumerator<T> GetEnumerator() { return new NodeEnumerator<T>(this); } } private class NodeEnumerator<T> : ILinedListEnumerator<T> { public Node<T> CurrentNode { get; set; } public NodeEnumerator(Node<T> node) { CurrentNode = node; } public T CurrentItem => CurrentNode.Item; public bool MoveNext() { if (CurrentNode!=null) return true; return false; } public bool SetNext() { if (CurrentNode.NodeItem != null) { CurrentNode = CurrentNode.NodeItem; return true; } else { CurrentNode = null; return false; } } /// <summary> /// 當迭代器內部存在非托管資源時,用於釋放資源 /// </summary> public void Dispose() { throw new NotImplementedException(); } } /// <summary> /// 鏈表迭代器接口約束 /// </summary> /// <typeparam name="T"></typeparam> public interface ILinkedListEnumerable<T> { ILinedListEnumerator<T> GetEnumerator(); } /// <summary> /// 鏈表單個迭代器 /// </summary> /// <typeparam name="T"></typeparam> public interface ILinedListEnumerator<T> : IDisposable { /// <summary> /// 當前迭代器元素 /// </summary> Node<T> CurrentNode { get; } /// <summary> /// 當前迭代器元素內容 /// </summary> T CurrentItem { get; } /// <summary> /// 是否可以進行下一步遍歷操作 /// </summary> /// <returns></returns> bool MoveNext(); /// <summary> /// 遍歷完當前鏈表元素,使其指向下一個元素 /// </summary> bool SetNext(); } }
4、實戰 雙向鏈表
雙向鏈表的應用場景很多,比如Redis的List就是使用雙向鏈表實現的.這種形式的鏈表更加的靈活.
修改代碼如下:
/// <summary> /// C#鏈表實現 /// </summary> public class LinkedList { static void Main(string[] args) { //生成對應的Node節點 var nodeFirst = new Node<string>(); var nodeSecond = new Node<string>(); var nodeThird = new Node<string>(); //構造節點內容 nodeFirst.Item = "one"; nodeSecond.Item = "two"; nodeThird.Item = "three"; //鏈接節點 nodeFirst.NextNode = nodeSecond; nodeSecond.NextNode = nodeThird; //注:這里nodeThird的NextNode指向null var nodeEnumerator = nodeFirst.GetEnumerator(); while (nodeEnumerator.MoveNext()) { //輸出當前節點的內容 Console.Write($"當前節點元素內容:{nodeEnumerator.CurrentItem} "); //輸出上一個節點的內容 Console.Write($"上一個節點元素內容:{nodeEnumerator.PreviousNode?.Item??"沒有上一個節點"} "); //這里如果當前節點的下一個節點不為空,則讓當前節點變為下一個節點 if (nodeEnumerator.SetNext()) Console.WriteLine($"下一個節點內容:{nodeEnumerator.CurrentNode.Item}"); else Console.WriteLine($"鏈表遍歷結束,下一個節點內容為null"); } Console.ReadKey(); } /// <summary> /// 節點對象,使用迭代器模式,實現鏈表的遍歷 /// </summary> /// <typeparam name="T"></typeparam> public class Node<T> : ILinkedListEnumerable<T> { public T Item { get; set; } public Node<T> NextNode { get; set; } public ILinedListEnumerator<T> GetEnumerator() { return new NodeEnumerator<T>(this); } } private class NodeEnumerator<T> : ILinedListEnumerator<T> { public Node<T> PreviousNode { get; set; } public Node<T> CurrentNode { get; set; } public NodeEnumerator(Node<T> node) { CurrentNode = node; } public T CurrentItem => CurrentNode.Item; public bool MoveNext() { if (CurrentNode!=null) return true; return false; } public bool SetNext() { if (CurrentNode.NextNode != null) { PreviousNode = CurrentNode; CurrentNode = CurrentNode.NextNode; return true; } else { CurrentNode = null; return false; } } /// <summary> /// 當迭代器內部存在非托管資源時,用於釋放資源 /// </summary> public void Dispose() { throw new NotImplementedException(); } } /// <summary> /// 鏈表迭代器接口約束 /// </summary> /// <typeparam name="T"></typeparam> public interface ILinkedListEnumerable<T> { ILinedListEnumerator<T> GetEnumerator(); } /// <summary> /// 鏈表單個迭代器 /// </summary> /// <typeparam name="T"></typeparam> public interface ILinedListEnumerator<T> : IDisposable { /// <summary> /// 上一個迭代器元素 /// </summary> Node<T> PreviousNode { get; } /// <summary> /// 當前迭代器元素 /// </summary> Node<T> CurrentNode { get; } /// <summary> /// 當前迭代器元素內容 /// </summary> T CurrentItem { get; } /// <summary> /// 是否可以進行下一步遍歷操作 /// </summary> /// <returns></returns> bool MoveNext(); /// <summary> /// 遍歷完當前鏈表元素,使其指向下一個元素 /// </summary> bool SetNext(); } }
5、通過雙向鏈表實現反向遍歷
如果沒有實現鏈表的雙向功能,實現反向遍歷的功能是不可能,實際上Redis的List是實現了這個功能的,所以這里我也實現下,tip:目前為止,所以的遍歷都是先進先出的,類似於隊列,所以如果實現了反向遍歷,從而該雙向鏈表同時也支持了先進后出的功能,類似於棧,為了分離正向和反向這個遍歷過程,所以我實現了兩個迭代器,分別為正向迭代器和反向迭代器.
代碼如下:
/// <summary> /// C#鏈表實現 /// </summary> public class LinkedList { static void Main(string[] args) { //生成對應的Node節點 var nodeFirst = new Node<string>(); var nodeSecond = new Node<string>(); var nodeThird = new Node<string>(); //構造節點內容 nodeFirst.Item = "one"; nodeSecond.Item = "two"; nodeThird.Item = "three"; //鏈接節點 nodeFirst.NextNode = nodeSecond; nodeSecond.NextNode = nodeThird; nodeSecond.PreviousNode = nodeFirst; nodeThird.PreviousNode = nodeSecond; //注:這里nodeThird的NextNode指向null var nodeEnumerator = nodeThird.GetNodeReverseEnumerator(); while (nodeEnumerator.MoveNext()) { //輸出當前節點的內容 Console.Write($"當前節點元素內容:{nodeEnumerator.CurrentItem} "); //輸出上一個節點的內容 Console.Write($"上一個節點元素內容:{nodeEnumerator.PreviousNode?.Item ?? "沒有上一個節點"} "); //這里如果當前節點的下一個節點不為空,則讓當前節點變為下一個節點 if (nodeEnumerator.SetNext()) Console.WriteLine($"下一個節點內容:{nodeEnumerator?.CurrentNode.Item}"); else Console.WriteLine($"鏈表遍歷結束,下一個節點內容為null"); } Console.ReadKey(); } /// <summary> /// 節點對象,使用迭代器模式,實現鏈表的遍歷 /// </summary> /// <typeparam name="T"></typeparam> public class Node<T> : ILinkedListEnumerable<T> { public T Item { get; set; } public Node<T> PreviousNode { get; set; } public Node<T> NextNode { get; set; } /// <summary> /// 獲取正向迭代器 /// </summary> /// <returns></returns> public ILinedListEnumerator<T> GetEnumerator() { return new NodeEnumerator<T>(this); } /// <summary> /// 獲取反向迭代器 /// </summary> /// <returns></returns> public ILinedListEnumerator<T> GetNodeReverseEnumerator() { return new NodeReverseEnumerator<T>(this); } } /// <summary> /// 正向迭代器 /// </summary> /// <typeparam name="T"></typeparam> private class NodeEnumerator<T> : ILinedListEnumerator<T> { public Node<T> PreviousNode { get; set; } public Node<T> CurrentNode { get; set; } public NodeEnumerator(Node<T> node) { CurrentNode = node; } public T CurrentItem => CurrentNode.Item; public bool MoveNext() { if (PreviousNode != null) return true; return false; } public bool SetNext() { if (CurrentNode.PreviousNode != null) { PreviousNode = CurrentNode; CurrentNode = CurrentNode.PreviousNode; return true; } else { CurrentNode = null; return false; } } /// <summary> /// 當迭代器內部存在非托管資源時,用於釋放資源 /// </summary> public void Dispose() { throw new NotImplementedException(); } } /// <summary> /// 反向迭代器 /// </summary> private class NodeReverseEnumerator<T>: ILinedListEnumerator<T> { public Node<T> PreviousNode { get; set; } public Node<T> CurrentNode { get; set; } public NodeReverseEnumerator(Node<T> node) { CurrentNode = node; } public T CurrentItem => CurrentNode.Item; public bool MoveNext() { if (CurrentNode != null) return true; return false; } public bool SetNext() { if (CurrentNode.PreviousNode != null) { PreviousNode = CurrentNode; CurrentNode = CurrentNode.PreviousNode; return true; } else { CurrentNode = null; return false; } } /// <summary> /// 當迭代器內部存在非托管資源時,用於釋放資源 /// </summary> public void Dispose() { throw new NotImplementedException(); } } /// <summary> /// 鏈表迭代器接口約束 /// </summary> /// <typeparam name="T"></typeparam> public interface ILinkedListEnumerable<T> { /// <summary> /// 正向迭代器 /// </summary> /// <returns></returns> ILinedListEnumerator<T> GetEnumerator(); /// <summary> /// 反向迭代器 /// </summary> /// <returns></returns> ILinedListEnumerator<T> GetNodeReverseEnumerator(); } /// <summary> /// 鏈表單個迭代器 /// </summary> /// <typeparam name="T"></typeparam> public interface ILinedListEnumerator<T> : IDisposable { /// <summary> /// 上一個迭代器元素 /// </summary> Node<T> PreviousNode { get; } /// <summary> /// 當前迭代器元素 /// </summary> Node<T> CurrentNode { get; } /// <summary> /// 當前迭代器元素內容 /// </summary> T CurrentItem { get; } /// <summary> /// 是否可以進行下一步遍歷操作 /// </summary> /// <returns></returns> bool MoveNext(); /// <summary> /// 遍歷完當前鏈表元素,使其指向下一個元素 /// </summary> bool SetNext(); } }