鏈表是一種重要的數據結構,是一種數據的存儲方式。鏈表由多個鏈表元素組成,每個元素稱為節點。鏈表存儲的物理結構可能是連續的,但也可能是無序的。但是鏈表之間的元素(節點)是有序的邏輯相連。
鏈表分為:單(向)鏈表、循環鏈表、雙向鏈表。
雖然有三種不同的鏈表,但是其中心思想(存儲的邏輯結構)是一樣的。筆者以單鏈表來分享其思想。
首先來看鏈表的節點是怎么定義的:代碼如下
class Node<T> //定義為泛型,根據用戶存儲需求決定存儲的數據類型 { public T Date { get; set; } //存儲的數據 public Node<T> NodeFront { get; set; } //前驅 public Node<T> NodeNext { get; set; } //后繼 public Node(T t) //構造函數 { Date = t; NodeFront = NodeNext = null; //剛開始初始化的節點沒有前驅和后繼 } public Node() //無參構造函數 { Date = default(T); //泛型默認的初始化方式 NodeFront = NodeNext = null; } }
節點類有三個屬性成員,分別是Date、NodeFront、NodeNext。其中NodeFront、NodeNext的類型也是Node<T>的引用類型,也就相當於NodeFront、NodeNext也是屬於節點的。由於是單向鏈表,我們只用到后繼NodeNext
比如我們創建三個節點:node1、node2、node3,然后輸出note2的值,代碼如下:
static void Main(string[] args) { Node<int> node1 = new Node<int>(1); Node<int> node2 = new Node<int>(2); Node<int> node3 = new Node<int>(3); node1.NodeNext = node2; node2.NodeNext = node3; //輸出node2的值 Console.WriteLine(node2.Date); Console.WriteLine(node1.NodeNext.Date); Console.ReadKey(); }
結果是輸出的值都是2。
所以說NodeNext也是屬於節點的,它就相當於node2節點。但是,我們在用單向鏈表的時候只有頭節點和尾節點,那么NodeNext就很有用了。只要我們知道頭節點和下標,就可以一步一步算出指定位置的值。
畫個圖幫助理解下:

單向鏈表存儲的邏輯結構其實有點像嵌套。node1的NodeNext成員保存了node2,node2的NodeNext成員又保存了node3,node3的NodeNext成員又保存了下一個節點直到尾節點的NodeNext保存的是null。
清楚了單向鏈表,實現其功能就很簡單了,用來用去就是用到NodeNext這個成員。
先創建一個MyList類,代碼如下:
class MyList<T> { private int listSize = 0; private Node<T> ListHead; //存儲鏈表的頭 private Node<T> ListTail; //存儲鏈表的尾 //為listSize添加一個只讀屬性 public int ListSize { get { return listSize; } } }
筆者在寫添加一個listSize只讀屬性的時候當時是這樣寫的:
public int ListSize { get; }
Tip:當時想把ListSize即當字段用,又當屬性用。但是發現,在類里面,字段ListSize也是不可寫入的。所以在寫只讀屬性時,字段和屬性還是分開寫好。
接下來,筆者分享其中一個功能的實現思想(有點懶= =),其實也算是最復雜的一個功能,會了這個其他都不是事兒了:在指定位置插入指定的元素:
代碼如下:
//向鏈表插入指定位置插入一個元素(前插) public bool Insert_Front(T date, uint position) { if (IsEmpty()) //鏈表為空 return false; else if (position > listSize) //超出鏈表長度或則為鏈表尾巴 return false; if (position == 1) //在鏈表頭插入 { Node<T> newNode = new Node<T>(date); //創建一個節點 newNode.NodeNext = ListHead; //新節點的后繼指向頭節點 ListHead = newNode; //新節點變成頭節點 ++listSize; return true; } else { Node<T> tempNode = ListHead; //創建一個臨時節點保存鏈表頭 Node<T> tempFrontNode = new Node<T>(); //創建一個節點,保存tempNode的上一個節點 uint ui = 1; while (ui < position && tempNode.NodeNext != null) { tempFrontNode = tempNode; //保存tempNode的上一個節點tempFrontNode(前插的時候會用到這個節點) tempNode = tempNode.NodeNext; //tempNode自身變成了下一個節點 ui++; } Node<T> newNode = new Node<T>(date); //創建新節點 tempFrontNode.NodeNext = newNode; //tempFrontNode的后繼等於新節點 newNode.NodeNext = tempNode; //新節點的后繼節點等於tempNode節點 ++listSize; //鏈表長度加1 return true; } }
畫張圖來腦補一下:(不表示在頭節點前面插入)

只要理解了節點鏈接的邏輯,所有的功能都會很好實現的。下面貼一下完整的代碼:
 
          
         using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 線性鏈表 { class Node<T> //定義為泛型,根據用戶存儲需求決定存儲的數據類型 { public T Date { get; set; } //存儲的數據 public Node<T> NodeFront { get; set; } //前驅 public Node<T> NodeNext { get; set; } //后繼 public Node(T t) //構造函數 { Date = t; NodeFront = NodeNext = null; //剛開始初始化的節點沒有前驅和后繼 } public Node() //無參構造函數 { Date = default(T); //泛型默認的初始化方式 NodeFront = NodeNext = null; } } class MyList<T> { private int listSize = 0; private Node<T> ListHead; //存儲鏈表的頭 private Node<T> ListTail; //存儲鏈表的尾 //為listSize添加一個只讀屬性 public int ListSize { get { return listSize; } } //判斷線性鏈表是否為空 public bool IsEmpty() { return ListHead == null ? true : false; } //向鏈表里添加一個元素 public void Push_In(T date) { Node<T> newNode = new Node<T>(date); //創建一個節點 if (IsEmpty()) //鏈表是否為空 ListHead = newNode; //鏈表為空時,添加的元素為頭節點 else ListTail.NodeNext = newNode; //否者原先的尾節點的NodeNext成員(后繼)指向新元素。 ListTail = newNode; //新元素變成尾節點 ++listSize; } //向鏈表插入指定位置插入一個元素(前插) public bool Insert_Front(T date, uint position) { if (IsEmpty()) //鏈表為空 return false; else if (position > listSize) //超出鏈表長度或則為鏈表尾巴 return false; if (position == 1) //在鏈表頭插入 { Node<T> newNode = new Node<T>(date); //創建一個節點 newNode.NodeNext = ListHead; //新節點的后繼指向頭節點 ListHead = newNode; //新節點變成頭節點 ++listSize; return true; } else { Node<T> tempNode = ListHead; //創建一個臨時節點保存鏈表頭 Node<T> tempFrontNode = new Node<T>(); //創建一個節點,保存tempNode的上一個節點 uint ui = 1; while (ui < position && tempNode.NodeNext != null) { tempFrontNode = tempNode; //保存tempNode的上一個節點tempFrontNode(前插的時候會用到這個節點) tempNode = tempNode.NodeNext; //tempNode自身變成了下一個節點 ui++; } Node<T> newNode = new Node<T>(date); //創建新節點 tempFrontNode.NodeNext = newNode; //tempFrontNode的后繼等於新節點 newNode.NodeNext = tempNode; //新節點的后繼節點等於tempNode節點 ++listSize; //鏈表長度加1 return true; } } //向鏈表插入指定位置插入一個元素(后插) public void Insert_Back(T date, uint position) { if (IsEmpty()) Console.WriteLine("鏈表為空"); else if (position > listSize) Console.WriteLine("越界了"); else { Node<T> newNode = new Node<T>(date); Node<T> tempNode = ListHead; Node<T> tempFrontNode = new Node<T>(); uint ui = 1; if (position == listSize) //如果在表尾 { ListTail.NodeNext = newNode; ListTail = newNode; } else { while (ui <= position && tempNode.NodeNext != null) { tempFrontNode = tempNode; tempNode = tempNode.NodeNext; ui++; } tempFrontNode.NodeNext = newNode; newNode.NodeNext = tempNode; } ++listSize; } } //在鏈表刪除一個指定元素 public bool Delete_Out(T date) { if (IsEmpty()) { Console.WriteLine("列表為空,無法刪除"); return false; } else { Node<T> tempNode = ListHead; //創建臨時節點,保存表頭 Node<T> tempFrontNode = new Node<T>(); //保存臨時節點的前驅 Node<T> tempNextNode = new Node<T>(); //保存臨時節點的后驅 uint ui = 1; while (!tempNode.Date.Equals(date) && ui <= listSize) { tempFrontNode = tempNode; if (tempNode.NodeNext != null) //如果不為表尾 { tempNode = tempNode.NodeNext; tempNextNode = tempNode.NodeNext; } ui++; }//end while if (ui > listSize) { Console.WriteLine("沒有找到指定元素"); return false; } else { if (ui == listSize) //刪除表尾 ListTail = tempFrontNode; else { tempFrontNode.NodeNext = tempNextNode; tempNextNode.NodeFront = tempFrontNode; } --listSize; GC.Collect(); //強制垃圾回首 return true; } }//end else } //輸出列表的元素 public void Print_Element() { if (IsEmpty()) { Console.WriteLine("列表為空"); } else { Node<T> tempNode = ListHead; uint i = 1; Console.Write("元素為:"); do { Console.Write("{0} ", tempNode.Date); tempNode = tempNode.NodeNext; i++; } while (i <= listSize); Console.WriteLine(); } } //查找指定位置的元素 public T GetElement(uint index) { T item = default(T); //默認初始化泛型 if (IsEmpty()) Console.WriteLine("鏈表為空"); else if (index > listSize) Console.WriteLine("越界了"); else { uint uCount = 1; Node<T> tempNode = ListHead; while (uCount != index && tempNode.NodeNext != null) { tempNode = tempNode.NodeNext; uCount++; } item = tempNode.Date; } return item; } } }
