【 C# 】(一) ------------- 泛型帶頭節點的單鏈表,雙向鏈表實現


在編程領域,數據結構與算法向來都是提升編程能力的重點。而一般常見的數據結構是鏈表,棧,隊列,樹等。事實上C#也已經封裝好了這些數據結構,在頭文件 System.Collections.Generic 中,直接創建並調用其成員方法就行。不過我們學習當然要知其然,亦知其所以然。

本文實現的是鏈表中的單鏈表和雙向鏈表,並且實現了一些基本方法

一. 定義一個鏈表接口 MyList

接口里聲明了我們要實現的方法:

	interface MyList<T>
	{
		int GetLength();							//獲取鏈表長度
		void Clear();								//清空鏈表				
		bool IsEmpty();								//判斷鏈表是否為空
		void Add(T item);							//在鏈表尾部添加新節點
		void AddPre(T item,int index);				//在指定節點前添加新節點
		void AddPost(T item,int index);				//在指定節點后添加新節點
		T Delete(int index);						//按索引刪除節點
		T Delete(T item,bool isSecond = true);		//按內容刪除節點,如果有多個內容相同點,則刪除第一個
		T this[int index] { get; }					//實現下標訪問
		T GetElem(int index);						//根據索引返回元素
		int GetPos(T item);							//根據元素返回索引地址
		void Print();								//打印
	}

 

二. 實現單鏈表

 

2.1 節點類

先定義一個單鏈表所用的節點類,Node。而且我們要實現泛型

先定義一個數據域和下一節點(“Next”),並進行封裝,然后給出數個重載構造器。這一步比較簡單,這里直接給出代碼

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 線性表
{
	/// <summary>
	/// 單向鏈表節點
	/// </summary>
	/// <typeparam name="T"></typeparam>
	class Node<T>
	{
		private T data;						//內容域
		private Node<T> next;				//下一節點

		public Node()
		{
			this.data = default(T);
			this.next = null;
		}

		public Node(T value)
		{
			this.data = value;
			this.next = null;
		}

		public Node(T value,Node<T> next)
		{
			this.data = value;
			this.next = next;
		}

		public T Data
		{
			get { return data; }
			set { data = value; }
		}

		public Node<T> Next
		{
			get { return next; }
			set { next = value; }
		}
	}
}

 

2.2 鏈表類

創建一個鏈表類,命名為 LinkList 並繼承 MyList。 


 先定義一個頭結點,尾節點和一個 count;

​ 

其中,head 表示該鏈表的頭部,不包含數據;

           tail 表示尾節點,指向該鏈表最后一個節點,當鏈表中只有 head 時,tail 指向 head。定義 tail 會方便接下來的操作

           count 用來表示該鏈表中除了 head 以外的節點個數

 

構造函數:

		/// <summary> /// 構造器 /// </summary> public LinkList() { head = new Node<T>(); tail = head; count = 0; }

在我們實現成員函數之前,先實現兩個特別的方法,因為在許多的成員方法中都要做兩個操作:

  • 判斷索引 index 是否合法,即是否小於0或者大於當前鏈表的節點個數
  • 尋找到 index 所代表的節點

 

①. 判斷索引是否合法,然后可以根據其返回的數值進行判斷操作

 

 

②. 尋找節點。

​ 

定義這兩個方法主要是它們的重復使用率高,所以把它們的代碼抽出來。

 相對於數組,鏈表的插入與刪除更方便,而查找卻更加費時,一般都是從頭結點開始遍歷鏈表,時間復雜度為 O(n) ,而跳躍鏈表則會對查詢進行優化,當然這會在下一篇中詳述。現在繼續來實現成員方法。

 

1. 獲取鏈表長度

​ 

 這個方法實際上是比較簡單的,因為 count 會隨着添加,刪除等操作自動增減,所以直接返回 count 就相當於 鏈表長度。

需要注意的是,本文中的 count 是不計算空頭結點的,即 head 不會計算入內

 

2. 清空鏈表


這里要注意對 tail 的操作,而 head.Next 原本所指的節點不再被引用后,會被GC自動回收

 

3. 判斷鏈表是否為空

因為本文實現的鏈表是帶空頭結點的,所以這里認為,當除了頭結點外沒有別的節點時,則為空鏈表

 

 

4. 在鏈表尾部添加節點

在鏈表尾添加節點一般考慮兩種情況:

  • 當前除了頭結點沒有別的節點,此時相當於創建第一個節點
  • 尋找到最后一個節點

對於帶空頭結點的鏈表來說,這兩種情況有着一樣的操作,只不過第一種情況要多做一步:讓 head 指向新創建的節點

​ 

定義了 tail 節點省去了 遍歷尋找最后節點的步驟,如果此時是空鏈表的話,tail 則指向 head

 

5. 在指定索引的前或后添加節點

這兩個方法的思路實際上相差無幾的

 

如圖,當 index 為 F 時:

  • AddPost: ① 找到 F 節點 ②創建 NEW 節點;③ NEW 節點指向 G;④ F 指向 NEW 節點
  • AddPre   :  ① 找到 E 節點 ②創建 NEW 節點;③ NEW 節點指向 F ;④ E 指向 NEW 節點

 AddPre 相當於 index - 1 處的 AddPost;AddPost 相當於 index + 1 處的 AddPre(當然,這是在 index -1 與 index + 1 合法的情況下)

​ 

 

 

6. 兩種刪除節點方法

  • 按索引刪除:找到索引所指節點,刪除
  • 按元素刪除:找元素所在的索引;當找不到該元素時表明鏈表中不存在應該刪除的節點,不執行刪除操作;當鏈表中存在多個相同的元素時,找到並刪除第一個


 

兩種刪除方法操作都是相似的,只是搜索節點的方法不同,刪除時要嚴格注意節點間指向的,即注意書寫代碼時的順序

 

​ 

 

 

 7. 實現下標訪問

這是個比較有趣的實現。前文說過對比於數組,鏈表勝於增減,弱於訪問。對鏈表實現下標式訪問,雖然它的內核依然是遍歷鏈表,然后返回節點,但在使用上會方便許多,如同使用數組一般。


 

8. 根據索引返回元素

這個和 GetNode 方法一致


 

9. 根據元素返回索引地址

​ 

這個方法也是比較簡單的,只是需要注意的一點是:while循環條件中 && 號兩端的條件不能調換位置。因為如果調換位置后,當鏈表遍歷到最后一個節點仍沒找到元素時,pstr 會被賦值下一節點(此時為NULL),然后循環繼續執行,執行到 !pstr.Data.Equals(item) 這一句時會報空指針,因為此時 pstr 就是空指針;還有因為這是泛型,所以判斷兩個值是否相等不能用 == 號,除非你重載 == 號。

 

10.打印鏈表


 

至此,所以的成員方法都實現了,先來測試一下。

1

.

 

 

​ 

 

其它功能讀者可以自行測試,完整代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 線性表
{
	class LinkList<T> : MyList<T>
	{
		private Node<T> head;			//頭結點
		private Node<T> tail;			//尾節點
		private int count;				//節點個數

		/// <summary>
		/// 構造器
		/// </summary>
		public LinkList()
		{
			head = new Node<T>();
			tail = head;
			count = 0;
		}

		/// <summary>
		/// 實現下標訪問法
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public T this[int index]
		{
			get
			{
				int i = IsIndexVaild(index);
				if(i == -1) return default(T);

				int k = 0;
				Node<T> pstr = head;
				while (k++ < index )
				{
					pstr = pstr.Next;
				}

				return pstr.Data;

			}
		}

		/// <summary>
		/// 在鏈表最末端添加新節點
		/// </summary>
		/// <param name="item"></param>
		public void Add(T item)
		{
			Node<T> tailNode = new Node<T>(item);
			tail.Next = tailNode;
			tail = tailNode;
			if (count == 0) head.Next = tailNode;
			count++;
		}


		/// <summary>
		/// 在第 index 號元素后插入一個節點
		/// <param name="item"></param>
		/// <param name="index"></param>
		public void AddPost(T item, int index)
		{
			int i = IsIndexVaild(index);
			if (i == -1) return;

			//找到索引元素
			Node<T> pstr = GetNode(index);

			//鏈接新節點
			Node<T> node = new Node<T>(item);
			node.Next = pstr.Next;
			pstr.Next = node;
			if (index == count) tail = node;
			count++;
			pstr = null;
		}


		/// <summary>
		/// 在第 index 號元素前插入一個節點
		/// </summary>
		/// <param name="item"></param>
		/// <param name="index"></param>
		public void AddPre(T item, int index)
		{
			int i = IsIndexVaild(index);
			if (i == -1) return;

			//找到索引的前一位元素
			Node<T> pstr = GetNode(index - 1);

			//鏈接新節點
			Node<T> node = new Node<T>(item);
			node.Next = pstr.Next;
			pstr.Next = node;
			count++;
			pstr = null;
		}


		/// <summary>
		/// 清空鏈表
		/// </summary>
		public void Clear()
		{
			head.Next = null;
			tail = head;
		}


		/// <summary>
		/// 刪除指定位置的元素
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public T Delete(int index)
		{
			int i = IsIndexVaild(index);
			if (i == -1) return default(T);

			//找到索引的前一位元素
			Node<T> pstr = GetNode(index - 1);

			if (pstr.Next == null) return default(T);

			Node<T> qstr = pstr.Next;
			pstr.Next = qstr.Next;
			T t = qstr.Data;
			pstr = null;
			qstr.Next = null;
			qstr = null;
			count--;
			return t;
		}

		/// <summary>
		/// 按內容刪除
		/// </summary>
		/// <param name="item"></param>
		/// <param name="isSecond"></param>
		/// <returns></returns>
		public T Delete(T item,bool isSecond = true)
		{

			int k = GetPos(item);
			if (k == -1) return default(T);
			int i = 0;

			Node<T> pstr = head;
			while (i++ < k -1)
			{
				pstr = pstr.Next;
			}
			Node<T> qstr = pstr.Next;
			pstr.Next = qstr.Next;
			T t = qstr.Data;
			pstr = null;
			qstr.Next = null;
			qstr = null;
			count--;
			return t;
		}

		/// <summary>
		/// 返回指定索引的元素
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public T GetElem(int index)
		{
			int i = IsIndexVaild(index);
			if (i == -1) return default(T);

			return GetNode(index).Data;
		}

		/// <summary>
		/// 返回鏈表長度
		/// </summary>
		/// <returns></returns>
		public int GetLength()
		{
			return count;
		}

		/// <summary>
		/// 根據元素返回其索引值
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		public int GetPos(T item)
		{
			int k = 0;
			Node<T> pstr = head.Next;
			while (pstr != null && item != null && !pstr.Data.Equals(item))
			{
				pstr = pstr.Next;
				k++;
			}

			if (pstr == null)
			{
				Console.WriteLine("所查找元素不存在");
				return -1;
			}

			return k ;
		}

		/// <summary>
		/// 判斷鏈表是否為空
		/// </summary>
		/// <returns></returns>
		public bool IsEmpty()
		{
			if (head == null || head.Next == null) return true;
			return false;
		}

		/// <summary>
		/// 打印
		/// </summary>
		public void Print()
		{
			Node<T> pstr = head.Next;
			int i = 1;
			while(pstr != null)
			{
				Console.WriteLine("第 " + i++ + "個元素是: " + pstr.Data);
				pstr = pstr.Next;
			}
		}

		/// <summary>
		/// 判斷索引是否錯誤
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public int IsIndexVaild(int index)
		{
			//判斷索引是否越界
			if (index < 0 || index > count)
			{
				Console.WriteLine("索引越界,不存在該元素");
				return -1;
			}
			return 0;
		}

		/// <summary>
		/// 根據索引找到元素
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public Node<T> GetNode(int index)
		{
			int k = 0;
			Node<T> pstr = head;
			while (k++ < index)
			{
				pstr = pstr.Next;
			}
			return pstr;
		}
	}
}

 

 

三. 雙向鏈表

雙向鏈表在思路上和單鏈表差不多,只是多了一個指向上一個節點的 Prev,所以代碼上要更小心地處理。具體就不多贅述了,直接給出代碼吧

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 線性表
{
	class DBNode<T>
	{
		private T data;
		private DBNode<T> next;
		private DBNode<T> prev;

		public DBNode()
		{
			this.data = default(T);
			this.next = null;
			this.prev = null;
		}

		public DBNode(T value)
		{
			this.data = value;
			this.next = null;
			this.prev = null;
		}

		public DBNode(T value, DBNode<T> next)
		{
			this.data = value;
			this.next = next;
			this.prev = null;
		}

		public T Data
		{
			get { return data; }
			set { data = value; }
		}

		public DBNode<T> Next
		{
			get { return next; }
			set { next = value; }
		}

		public DBNode<T> Prev
		{
			get { return prev; }
			set { prev = value; }
		}
	}
}

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 線性表
{
	class DBLinkList<T> : MyList<T>
	{
		private DBNode<T> head;
		private DBNode<T> tail;
		private int count;

		/// <summary>
		/// 構造器
		/// </summary>
		public DBLinkList()
		{
			head = new DBNode<T>();
			tail = head;
			count = 0;
		}

		/// <summary>
		/// 實現下標訪問法
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public T this[int index]
		{
			get
			{
				int i = IsIndexVaild(index);
				if (i == -1) return default(T);

				int k = 0;
				DBNode<T> pstr = head;
				while (k++ < index)
				{
					pstr = pstr.Next;
				}

				return pstr.Data;
			}
		}

		/// <summary>
		/// 在鏈表最末端添加新節點
		/// </summary>
		/// <param name="item"></param>
		public void Add(T item)
		{
			if (count == 0)
			{
				DBNode<T> DbNode = new DBNode<T>(item);
				DbNode.Prev = head;
				head.Next = DbNode;
				tail = DbNode;
				count++;
				return;
			}

			DBNode<T> tailDBNode = new DBNode<T>(item);
			tailDBNode.Prev = tail;
			tail.Next = tailDBNode;
			tail = tailDBNode;
			count++;
		}


		/// <summary>
		/// 在第 index 號元素后插入一個節點,index 為 1,2,3,4.....
		/// </summary>
		/// <param name="item"></param>
		/// <param name="index"></param>
		public void AddPost(T item, int index)
		{
			//判斷索引是否越界
			int i = IsIndexVaild(index);
			if (i == -1) return;

			//找到索引元素
			DBNode<T> pstr = GetNode(index);

			//鏈接新節點
			DBNode<T> newNode = new DBNode<T>(item);
			newNode.Next = pstr.Next;
			newNode.Prev = pstr;
			if(pstr.Next != null) pstr.Next.Prev = newNode;
			pstr.Next = newNode;

			//如果是在最后節點添加
			if (index == count) tail = newNode;
			count++;
			pstr = null;
		}


		/// <summary>
		/// 在第 index 號元素前插入一個節點,index 為 1,2,3,4.....
		/// </summary>
		/// <param name="item"></param>
		/// <param name="index"></param>
		public void AddPre(T item, int index)
		{
			//判斷索引是否越界
			int i = IsIndexVaild(index);
			if (i == -1) return;


			//找到索引的前一位元素
			DBNode<T> pstr = GetNode(index - 1);

			//鏈接新節點
			DBNode<T> newNode = new DBNode<T>(item);
			newNode.Next = pstr.Next;
			newNode.Prev = pstr;
			pstr.Next.Prev = newNode;
			pstr.Next = newNode;
			count++;
			pstr = null;

			//在 index 處AddPre相當於在 index - 1 處 AddPost,不過並不需要判斷尾節點
		}


		/// <summary>
		/// 清空鏈表
		/// </summary>
		public void Clear()
		{
			head.Next = null;
			tail = head;
		}


		/// <summary>
		/// 刪除指定位置的元素
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public T Delete(int index)
		{
			//判斷索引是否越界
			int i = IsIndexVaild(index);
			if (i == -1) return default(T);

			//找到索引的前一位元素
			DBNode<T> pstr = head;
			int k = 0;
			while (k++ < index - 1 && pstr != null)
			{
				pstr = pstr.Next;
			}

			if (pstr.Next == null) return default(T);

			DBNode<T> qstr = pstr.Next;
			T t = qstr.Data;

			pstr.Next = qstr.Next;
			qstr.Next.Prev = pstr;		

			pstr = null;
			qstr.Next = null;
			qstr = null;
			count--;
			return t;
		}

		/// <summary>
		/// 按內容刪除
		/// </summary>
		/// <param name="item"></param>
		/// <param name="isSecond"></param>
		/// <returns></returns>
		public T Delete(T item,bool isSecond = true)
		{

			int k = GetPos(item);
			if (k == -1) return default(T);
			int i = 0;

			DBNode<T> pstr = head;
			while (i++ < k - 1)
			{
				pstr = pstr.Next;
			}

			DBNode<T> qstr = pstr.Next;
			T t = qstr.Data;

			pstr.Next = qstr.Next;
			if(qstr.Next != null) qstr.Next.Prev = pstr;

			pstr = null;
			qstr.Next = null;
			qstr = null;
			count--;
			return t;
		}

		/// <summary>
		/// 返回指定索引的元素
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public T GetElem(int index)
		{
			int i = IsIndexVaild(index);
			if (i == -1) return default(T);

			int k = 0;
			DBNode<T> pstr = head;
			while (k++ < index)
			{
				pstr = pstr.Next;
			}

			return pstr.Data;
		}

		/// <summary>
		/// 返回鏈表長度
		/// </summary>
		/// <returns></returns>
		public int GetLength()
		{
			return count;
		}

		/// <summary>
		/// 根據元素返回其索引值
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		public int GetPos(T item)
		{
			int k = 0;
			DBNode<T> pstr = head.Next;
			while (pstr != null && item != null && !pstr.Data.Equals(item))
			{
				pstr = pstr.Next;
				k++;
			}

			if (pstr == null)
			{
				Console.WriteLine("所查找元素不存在");
				return -1;
			}

			return k;
		}

		/// <summary>
		/// 判斷鏈表是否為空
		/// </summary>
		/// <returns></returns>
		public bool IsEmpty()
		{
			if (head == null || head.Next == null) return true;
			return false;
		}

		/// <summary>
		/// 打印
		/// </summary>
		public void Print()
		{
			DBNode<T> pstr = head.Next;
			while (pstr != null)
			{
				Console.WriteLine(pstr.Data);
				pstr = pstr.Next;
			}
		}

		/// <summary>
		/// 判斷索引是否錯誤
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public int IsIndexVaild(int index)
		{
			//判斷索引是否越界
			if (index < 0 || index > count)
			{
				Console.WriteLine("索引越界,不存在該元素");
				return -1;
			}
			return 0;
		}

		/// <summary>
		/// 根據索引找到元素
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public DBNode<T> GetNode(int index)
		{
			int k = 0;
			DBNode<T> pstr = head;
			while (k++ < index)
			{
				pstr = pstr.Next;
			}
			return pstr;
		}
	}
}

 

總結

事實上,鏈表是一種比較簡單且常用的數據結構。實現起來並不困難,只是要小心謹慎。下一篇會說到跳躍鏈表,跳躍鏈表的效率更高。好了,希望本文能對大家有所幫助


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM