在編程領域,數據結構與算法向來都是提升編程能力的重點。而一般常見的數據結構是鏈表,棧,隊列,樹等。事實上C#也已經封裝好了這些數據結構,在頭文件 System.Collections.Generic 中,直接創建並調用其成員方法就行。不過我們學習當然要知其然,亦知其所以然。
本文實現的是鏈表中的單鏈表和雙向鏈表,並且實現了一些基本方法
一. 定義一個鏈表接口 MyList
接口里聲明了我們要實現的方法:
二. 實現單鏈表
2.1 節點類
先定義一個單鏈表所用的節點類,Node。而且我們要實現泛型
先定義一個數據域和下一節點(“Next”),並進行封裝,然后給出數個重載構造器。這一步比較簡單,這里直接給出代碼
2.2 鏈表類
創建一個鏈表類,命名為 LinkList 並繼承 MyList。
先定義一個頭結點,尾節點和一個 count;
其中,head 表示該鏈表的頭部,不包含數據;
tail 表示尾節點,指向該鏈表最后一個節點,當鏈表中只有 head 時,tail 指向 head。定義 tail 會方便接下來的操作
count 用來表示該鏈表中除了 head 以外的節點個數
構造函數:
在我們實現成員函數之前,先實現兩個特別的方法,因為在許多的成員方法中都要做兩個操作:
- 判斷索引 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
.
其它功能讀者可以自行測試,完整代碼:
三. 雙向鏈表
雙向鏈表在思路上和單鏈表差不多,只是多了一個指向上一個節點的 Prev,所以代碼上要更小心地處理。具體就不多贅述了,直接給出代碼吧
總結
事實上,鏈表是一種比較簡單且常用的數據結構。實現起來並不困難,只是要小心謹慎。下一篇會說到跳躍鏈表,跳躍鏈表的效率更高。好了,希望本文能對大家有所幫助