C# 演算法之連結串列、雙向連結串列以及正向反向遍歷實現
阿新 • • 發佈:2019-01-13
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(); } }