一、數據結構的一般概念
1.掌握數據結構的基本概念和術語。
數據:是描述客觀事物的符號,是計算機中可以操作的對象,是能被計算機識別,並輸入給計算機處理的符號集合。
數據元素:是組成數據的,有一定意義的基本單位,在計算機中通常作為整體處理。也被稱為記錄。
數據項:一個數據元素可以由若干個數據項組成。數據項是數據不可分割的最小單位。
數據對象:是性質相同的數據元素的集合,是數據的子集。
數據結構:是相互之間存在一種或多種特定關系的數據元素的集合。
邏輯結構:是指數據對象中數據元素之間的相互關系。
1. 集合結構 集合結構中的數據元素除了同屬於一個集合外,它們之間沒有其他關系。
2. 線性結構 線性結構中的數據元素之間是一對一的關系。
3. 樹形結構 樹形結構中的數據元素之間存在一種一對多的層次關系。
4. 圖形結構 圖形結構的數據元素是多對多的關系。
物理結構:是指數據的邏輯結構在計算機中的存儲形式。
1. 順序存儲結構:是把數據元素存放在地址連續的存儲單元里,其數據間的邏輯關系和物理關系式一致的。
2. 鏈式存儲結構:是把數據元素放在任意的存儲單元里,這組存儲單元可以是連續的,也可以是不連續的。
2.了解抽象數據類型的概念。
數據類型:是指一組性質相同的值的集合以及在此集合上的一些操作的總稱。
抽象數據類型:是指一個數學模型及定義在該模型上的一組操作。

3.掌握算法的特性,算法的描述和算法的分析。
算法:是解決特定問題求解步驟的描述,在計算機中表現為指令的有限序列,並且每條指令表示一個或多個操作。
算法的特性:輸入輸出,有窮性,確定性,可行性。
算法的時間復雜度:T(n) = O(f(n))

常數階:O(1) 線性階:O(n) 對數階:O(logn) 平方階:O(m*n)


算法的空間復雜度:
二、線性表
1.理解線性表的邏輯結構。
線性表:零個或多個數據元素的有限序列。
2.掌握線性表的順序存貯結構和鏈式存貯結構;掌握線性表基本操作的實現。
線性表的順序存儲結構:指的是用一段地址連續的存儲單元依次存儲線性表的數據元素。

線性表的鏈式存儲結構:n個結點鏈結成一個鏈表,即為線性表的鏈式存儲結構。
單鏈表:鏈表的每個結點只包含一個指針域。
頭指針:鏈表中第一個結點的存儲位置。


靜態鏈表:用數組描述的鏈表。

循環鏈表:

雙向鏈表:

3.了解線性表的應用。
三、其他線性結構
1.掌握棧的定義、棧的存貯結構及基本操作的實現。
棧:是限定僅在表尾進行插入和刪除操作的線性表。
棧的順序存儲結構,棧的鏈式存儲結構

2.理解用棧實現表達式的求值,遞歸過程及其實現。




3.掌握隊列的定義、存貯結構及基本操作的實現
隊列:是指允許在一端進行插入操作,另一端進行刪除操作的線性表。
循環隊列:隊列的這種頭尾相接的順序存儲結構。
鏈隊列:
4.理解串的邏輯定義及其基本操作;理解串的存貯結構。
串:是由零個或多個字符組成的有限序列,又叫字符串。


KMP模式匹配算法
5.理解數組的定義、數組的順序存貯結構及矩陣的存貯壓縮。
6.理解廣義表的定義及存貯結構。
四、樹和二叉樹
1.掌握樹型結構的定義。
樹:
度:結點擁有的子樹數稱為結點的度。
深度:樹中結點的最大層次稱為樹的深度或高度。
2.掌握二叉樹的定義、性質及各種存貯結構。
二叉樹:

完全二叉樹:

性質:





3.掌握遍歷二叉樹、線索二叉樹及其他基本操作。
前序遍歷:根結點,左子樹,右子樹。

中序遍歷:左子樹,根結點,右子樹。

后序遍歷:左子樹,右子樹,根節點

層序遍歷:從上到下,從左到右,逐層遍歷


4.掌握樹、森林與二叉樹的相互轉換;理解樹的遍歷;掌握哈夫曼樹及其應用。
樹轉化為二叉樹:

森林轉化為二叉樹:

二叉樹轉換為樹:

二叉樹轉為森林:

樹的遍歷:


森林的遍歷:




哈夫曼樹:
壓縮編碼:哈夫曼編碼,帶權路徑最小的二叉樹叫哈夫曼樹

五、圖
1.掌握圖的定義和術語。
圖:

頂點,











2.掌握圖的存貯結構;理解圖的基本操作。





3.掌握圖的遍歷算法;了解利用圖的遍歷解決圖的應用問題。



4.理解圖的有關應用:求最小生成樹、求最短路徑、拓撲排序及關鍵路徑等算法的基本思想。








六、查找

1.掌握靜態查找表。
private bool Find(TKey key, bool add, bool set, ref TValue value) { int hashCode = GetKeyHashCode(key); for (int i = buckets[hashCode % buckets.Length] - 1; i >= 0; i = slots[i].next) { if (slots[i].hashCode == hashCode && AreKeysEqual(slots[i].key, key)) { if (set) { slots[i].value = value; return true; } else { value = slots[i].value; return true; } } } }

1.稠密索引
2.分塊索引
3.倒排索引



2.掌握二叉排序樹和平衡二叉樹。
動態查找


3.理解B-樹;了解B+樹。






4.掌握哈希表。


public virtual bool ContainsKey(Object key) { if (key == null) { throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key")); } Contract.EndContractBlock(); uint seed; uint incr; // Take a snapshot of buckets, in case another thread resizes table bucket[] lbuckets = buckets; uint hashcode = InitHash(key, lbuckets.Length, out seed, out incr); int ntry = 0; bucket b; int bucketNumber = (int) (seed % (uint)lbuckets.Length); do { b = lbuckets[bucketNumber]; if (b.key == null) { return false; } if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && KeyEquals (b.key, key)) return true; bucketNumber = (int) (((long)bucketNumber + incr)% (uint)lbuckets.Length); } while (b.hash_coll < 0 && ++ntry < lbuckets.Length); return false; }
5.掌握各種查找方法的時間性能分析。
七、內部排序
1.掌握直接插入排序、希爾排序、冒泡排序、快速排序、簡單選擇排序、堆排序、歸並排序;理解基數排序。

2.學會各種內部排序方法的比較(時間復雜度、空間復雜度、穩定性)。

public static class SortExtention { #region 冒泡排序 /* * 已知一組無序數據a[1]、a[2]、……a[n],需將其按升序排列。首先比較a[1]與a[2]的值,若a[1]大於a[2]則交換兩者的值,否則不變。 * 再比較a[2]與a[3]的值,若a[2]大於a[3]則交換兩者的值,否則不變。再比較a[3]與a[4],以此類推,最后比較a[n-1]與a[n]的值。 * 這樣處理一輪后,a[n]的值一定是這組數據中最大的。再對a[1]~a[n-1]以相同方法處理一輪,則a[n-1]的值一定是a[1]~a[n-1]中最大的。 * 再對a[1]~a[n-2]以相同方法處理一輪,以此類推。共處理n-1輪后a[1]、a[2]、……a[n]就以升序排列了。 * 降序排列與升序排列相類似,若a[1]小於a[2]則交換兩者的值,否則不變,后面以此類推。 * 總的來講,每一輪排序后最大(或最小)的數將移動到數據序列的最后,理論上總共要進行n(n-1)/2次交換。 * 優點:穩定 * 時間復雜度:理想情況下(數組本來就是有序的),此時最好的時間復雜度為o(n),最壞的時間復雜度(數據反序的),此時的時間復雜度為o(n*n) 。 * 冒泡排序的平均時間負責度為o(n*n). * 缺點:慢,每次只移動相鄰的兩個元素。 */ /// <summary> /// 冒泡排序,結果升序排列 /// <para>調用:arry.BubbleSort();</para> /// </summary> /// <param name="arry">要排序的整數數組</param> public static void BubbleSort(this int[] arry) { for (int i = 0; i < arry.Length; i++) { for (int j = 0; j < arry.Length - 1 - i; j++) { //比較相鄰的兩個元素,如果前面的比后面的大,則交換位置 if (arry[j] > arry[j + 1]) { int temp = arry[j + 1]; arry[j + 1] = arry[j]; arry[j] = temp; } } } } #endregion #region 快速排序 /*** * 設要排序的數組是A[0]……A[N-1],首先任意選取一個數據(通常選用數組的第一個數)作為關鍵數據, * 然后將所有比它小的數都放到它前面,所有比它大的數都放到它后面,這個過程稱為一趟快速排序。 * 值得注意的是,快速排序不是一種穩定的排序算法,也就是說,多個相同的值的相對位置也許會在算法結束時產生變動。 一趟快速排序的算法是: 1)設置兩個變量i、j,排序開始的時候:i=0,j=N-1; 2)以第一個數組元素作為關鍵數據,賦值給key,即key=A[0]; 3)從j開始向前搜索,即由后開始向前搜索(j–),找到第一個小於key的值A[j],將A[j]和A[i]互換; 4)從i開始向后搜索,即由前開始向后搜索(i++),找到第一個大於key的A[i],將A[i]和A[j]互換; 5)重復第3、4步,直到i=j; (3,4步中,沒找到符合條件的值,即3中A[j]不小於key,4中A[i]不大於key的時候改變j、i的值, * 使得j=j-1,i=i+1,直至找到為止。找到符合條件的值,進行交換的時候i, j指針位置不變。另外,i==j這一過程一定正好是i+或j-完成的時候,此時令循環結束)。 */ /// <summary> /// 快速排序 /// <para>調用:arry.QuickSort(0, arry.Length-1 );</para> /// </summary> /// <param name="arry">要排序的數組</param> /// <param name="left">低位</param> /// <param name="right">高位</param> public static void QuickSort(this int[] arry, int left, int right) { //左邊索引小於右邊,則還未排序完成 if (left < right) { //取中間的元素作為比較基准,小於他的往左邊移,大於他的往右邊移 int middle = arry[(left + right) / 2]; int i = left - 1; int j = right + 1; while (true) { //移動下標,左邊的往右移動,右邊的向左移動 while (arry[++i] < middle && i < right) ; while (arry[--j] > middle && j > 0) ; if (i >= j) break; //交換位置 int number = arry[i]; arry[i] = arry[j]; arry[j] = number; } QuickSort(arry, left, i - 1); QuickSort(arry, j + 1, right); } } #endregion #region 直接插入排序 /** * 每次從無序表中取出第一個元素,把它插入到有序表的合適位置,使有序表仍然有序。 * 第一趟比較前兩個數,然后把第二個數按大小插入到有序表中; 第二趟把第三個數據與前兩個數從前向后掃描,把第三個數按大小插入到有序表中; * 依次進行下去,進行了(n-1)趟掃描以后就完成了整個排序過程。 * 直接插入排序屬於穩定的排序,最壞時間復雜性為O(n^2),空間復雜度為O(1)。 * 直接插入排序是由兩層嵌套循環組成的。外層循環標識並決定待比較的數值。內層循環為待比較數值確定其最終位置。 * 直接插入排序是將待比較的數值與它的前一個數值進行比較,所以外層循環是從第二個數值開始的。 * 當前一數值比待比較數值大的情況下繼續循環比較,直到找到比待比較數值小的並將待比較數值置入其后一位置,結束該次循環。 * 值得注意的是,我們必需用一個存儲空間來保存當前待比較的數值,因為當一趟比較完成時, * 我們要將待比較數值置入比它小的數值的后一位 插入排序類似玩牌時整理手中紙牌的過程。 * 插入排序的基本方法是:每步將一個待排序的記錄按其關鍵字的大小插到前面已經排序的序列中的適當位置,直到全部記錄插入完畢為止。 */ /// <summary> /// 直接插入排序 /// <para>調用:arry.InsertSort();</para> /// </summary> /// <param name="arry">要排序的數組</param> public static void InsertSort(this int[] arry) { //直接插入排序是將待比較的數值與它的前一個數值進行比較,所以外層循環是從第二個數值開始的 for (int i = 1; i < arry.Length; i++) { //如果當前元素小於其前面的元素 if (arry[i] < arry[i - 1]) { //用一個變量來保存當前待比較的數值,因為當一趟比較完成時,我們要將待比較數值置入比它小的數值的后一位 int temp = arry[i]; int j = 0; for (j = i - 1; j >= 0 && temp < arry[j]; j--) { arry[j + 1] = arry[j]; } arry[j + 1] = temp; } } } #endregion #region 希爾排序 /** * 希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序算法的一種更高效的改進版本。 * 希爾排序是非穩定排序算法。該方法因DL.Shell於1959年提出而得名。 * 希爾排序是基於插入排序的以下兩點性質而提出改進方法的: * 插入排序在對幾乎已經排好序的數據操作時,效率高,即可以達到線性排序的效率。 * 但插入排序一般來說是低效的,因為插入排序每次只能將數據移動一位。 基本思想: * 先取一個小於n的整數d1作為第一個增量,把文件的全部記錄分組。所有距離為d1的倍數的記錄放在同一個組中。 * 先在各組內進行直接插入排序;然后,取第二個增量d2<d1重復上述的分組和排序,直至所取的增量 =1( < …<d2<d1), * 即所有記錄放在同一組中進行直接插入排序為止。 * 該方法實質上是一種分組插入方法 * 比較相隔較遠距離(稱為增量)的數,使得數移動時能跨過多個元素, * 則進行一次比[2] 較就可能消除多個元素交換。D.L.shell於1959年在以他名字命名的排序算法中實現了這一思想。 * 算法先將要排序的一組數按某個增量d分成若干組,每組中記錄的下標相差d.對每組中全部元素進行排序, * 然后再用一個較小的增量對它進行,在每組中再進行排序。當增量減到1時,整個要排序的數被分成一組,排序完成。 * 一般的初次取序列的一半為增量,以后每次減半,直到增量為1。 */ /// <summary> /// 希爾排序 /// <para>調用:arry.ShellSort();</para> /// </summary> /// <param name="arry">待排序的數組</param> public static void ShellSort(this int[] arry) { int length = arry.Length; for (int h = length / 2; h > 0; h = h / 2) { //here is insert sort for (int i = h; i < length; i++) { int temp = arry[i]; if (temp < arry[i - h]) { for (int j = 0; j < i; j += h) { if (temp < arry[j]) { temp = arry[j]; arry[j] = arry[i]; arry[i] = temp; } } } } } } #endregion #region 簡單選擇排序 /** * 設所排序序列的記錄個數為n。i取1,2,…,n-1,從所有n-i+1個記錄(Ri,Ri+1,…,Rn)中找出排序碼最小的記錄, * 與第i個記錄交換。執行n-1趟 后就完成了記錄序列的排序。 * 在簡單選擇排序過程中,所需移動記錄的次數比較少。最好情況下, * 即待排序記錄初始狀態就已經是正序排列了,則不需要移動記錄。 * 最壞情況下,即待排序記錄初始狀態是按逆序排列的,則需要移動記錄的次數最多為3(n-1)。 * 簡單選擇排序過程中需要進行的比較次數與初始狀態下待排序的記錄序列的排列情況無關。 * 當i=1時,需進行n-1次比較;當i=2時,需進行n-2次比較;依次類推, * 共需要進行的比較次數是(n-1)+(n-2)+…+2+1=n(n-1)/2,即進行比較操作的時間復雜度為O(n^2), * 進行移動操作的時間復雜度為O(n)。 */ /// <summary> /// 簡單選擇排序 /// <para>調用:arry.SimpleSelectSort();</para> /// </summary> /// <param name="arry">待排序的數組</param> public static void SimpleSelectSort(this int[] arry) { int tmp = 0; int t = 0;//最小數標記 for (int i = 0; i < arry.Length; i++) { t = i; for (int j = i + 1; j < arry.Length; j++) { if (arry[t] > arry[j]) { t = j; } } tmp = arry[i]; arry[i] = arry[t]; arry[t] = tmp; } } #endregion #region 堆排序 /** * 堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法, * 它是選擇排序的一種。可以利用數組的特點快速定位指定索引的元素。 * 堆分為大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值, * 即A[PARENT[i]] >= A[i]。在數組的非降序排序中,需要使用的就是大根堆, * 因為根據大根堆的要求可知,最大的值一定在堆頂。 */ /// <summary> /// 堆排序 /// <para>調用:arry.HeapSort(arry.Length);</para> /// </summary> /// <param name="arry">待排序的數組</param> /// <param name="top"></param> public static void HeapSort(this int[] arry, int top) { //List<int> topNode = new List<int>(); for (int i = arry.Length / 2 - 1; i >= 0; i--) { HeapAdjust(arry, i, arry.Length); } for (int i = arry.Length - 1; i >= arry.Length - top; i--) { int temp = arry[0]; arry[0] = arry[i]; arry[i] = temp; HeapAdjust(arry, 0, i); } } /// <summary> /// 構建堆 /// </summary> /// <param name="arry"></param> /// <param name="parent"></param> /// <param name="length"></param> private static void HeapAdjust(int[] arry, int parent, int length) { int temp = arry[parent]; int child = 2 * parent + 1; while (child < length) { if (child + 1 < length && arry[child] < arry[child + 1]) child++; if (temp >= arry[child]) break; arry[parent] = arry[child]; parent = child; child = 2 * parent + 1; } arry[parent] = temp; } #endregion #region 歸並排序 /** * 歸並排序是建立在歸並操作上的一種有效的排序算法,該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。 * 將已有序的子序列合並,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序 * 。若將兩個有序表合並成一個有序表,稱為二路歸並。 * 歸並過程為:比較a[i]和a[j]的大小,若a[i]≤a[j],則將第一個有序表中的元素a[i]復制到r[k]中, * 並令i和k分別加上1;否則將第二個有序表中的元素a[j]復制到r[k]中,並令j和k分別加上1, * 如此循環下去,直到其中一個有序表取完,然后再將另一個有序表中剩余的元素復制到r中從下標k到下標t的單元。 * 歸並排序的算法我們通常用遞歸實現,先把待排序區間[s,t]以中點二分,接着把左邊子區間排序,再把右邊子區間排序, * 最后把左區間和右區間用一次歸並操作合並成有序的區間[s,t]。 * 歸並操作(merge),也叫歸並算法,指的是將兩個順序序列合並成一個順序序列的方法。 * 如 設有數列{6,202,100,301,38,8,1} * 初始狀態:6,202,100,301,38,8,1 * 第一次歸並后:{6,202},{100,301},{8,38},{1},比較次數:3; * 第二次歸並后:{6,100,202,301},{1,8,38},比較次數:4; * 第三次歸並后:{1,6,8,38,100,202,301},比較次數:4; * 總的比較次數為:3+4+4=11,; * 逆序數為14; * 歸並操作的工作原理如下: * 第一步:申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並后的序列 * 第二步:設定兩個指針,最初位置分別為兩個已經排序序列的起始位置 * 第三步:比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置 * 重復步驟3直到某一指針超出序列尾 * 將另一序列剩下的所有元素直接復制到合並序列尾 */ /// <summary> /// 歸並排序 /// <para>調用:arry.MergeSort(0, arry.Length);</para> /// </summary> /// <param name="arry">待排序數組</param> /// <param name="first"></param> /// <param name="last"></param> public static void MergeSort(this int[] arry, int first, int last) { if (first + 1 < last) { int mid = (first + last) / 2; MergeSort(arry, first, mid); MergeSort(arry, mid, last); Merger(arry, first, mid, last); } } /// <summary> /// 歸並 /// </summary> /// <param name="arry"></param> /// <param name="first"></param> /// <param name="mid"></param> /// <param name="last"></param> private static void Merger(int[] arry, int first, int mid, int last) { Queue<int> tempV = new Queue<int>(); //設置indexA,並掃描subArray1 [first,mid] //設置indexB,並掃描subArray2 [mid,last] int indexA = first; int indexB = mid; //在沒有比較完兩個子標的情況下,比較 v[indexA]和v[indexB] //將其中小的放到臨時變量tempV中 while (indexA < mid && indexB < last) { if (arry[indexA] < arry[indexB]) { tempV.Enqueue(arry[indexA]); indexA++; } else { tempV.Enqueue(arry[indexB]); indexB++; } } //復制沒有比較完子表中的元素 while (indexA < mid) { tempV.Enqueue(arry[indexA]); indexA++; } while (indexB < last) { tempV.Enqueue(arry[indexB]); indexB++; } int index = 0; while (tempV.Count > 0) { arry[first + index] = tempV.Dequeue(); index++; } } #endregion #region 基數排序 /** * 基數排序(radix sort)屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort, * 顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些“桶”中,藉以達到排序的作用, * 基數排序法是屬於穩定性的排序,其時間復雜度為O (nlog(r)m),其中r為所采取的基數, * 而m為堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法。 */ /// <summary> /// 基數排序 /// <para>約定:待排數字中沒有0,如果某桶內數字為0則表示該桶未被使用,輸出時跳過即可</para> /// <para>調用:arry.RadixSort();</para> /// </summary> /// <param name="arry">待排數組</param> /// <param name="array_x">桶數組第一維長度</param> /// <param name="array_y">桶數組第二維長度</param> public static void RadixSort(this int[] arry, int array_x = 10, int array_y = 100) { /* 最大數字不超過999999999...(array_x個9) */ for (int i = 0; i < array_x; i++) { int[,] bucket = new int[array_x, array_y]; foreach (var item in arry) { int temp = (item / (int)Math.Pow(10, i)) % 10; for (int l = 0; l < array_y; l++) { if (bucket[temp, l] == 0) { bucket[temp, l] = item; break; } } } for (int o = 0, x = 0; x < array_x; x++) { for (int y = 0; y < array_y; y++) { if (bucket[x, y] == 0) continue; arry[o++] = bucket[x, y]; } } } } #endregion }
參考書目:
1. 《數據結構》(C語言版),嚴蔚敏 吳偉民編著,清華大學出版社,2006.3
2. 《數據結構實用教程》(C++版),萬健主編,電子工業出版社,2011.1
