一:數據結構概論
- 在數據結構中數據分為兩種關系,一種時線性,一種是非線性
- 線性關系,比如一張學生登記表。
- 非線性關系,比如文件夾是樹型關系,比如計算機網絡是圖關系。
數據結構包括:
- 數據的存儲
- 物理結構:數據在計算機內的存儲表示
- 數據之間的關系
- 邏輯結構:數據之間的邏輯關系。分為兩種一種是順序存儲結構,一種是非順序存儲結構。順序結構一般用一維數組體現數據之間的關系。非順序存儲結構一般采用指針實現數據之間的關系,包括鏈式存儲結構和散列結構,索引存儲結構等。鏈式存儲利用指針直接表示數據元素之間的關系。散列結構是根據節點的關鍵字,利用散列函數直接計算出該節點的存儲地址。索引存儲結構是在存儲節點信息的同時,還建立附加的索引表。索引結構分為稠密索引和稀疏索引。
- 數據的操作。在各種結構上的算法。
數據類型:一個值的集合以及在這些值上定義的一組操作的總稱。
算法特征:
- 正確性
- 確定性
- 有窮性
- 輸入
- 輸出
效率和低存儲量兩者通常情況下是矛盾的。
二:線性表
線性表定義:每個數據元素最多只有一個直接前趨,每個數據元素最多只有一個直接后繼,只有第一個數據元素沒有直接前趨,最后一個數據元素沒有直接后繼。
線性表的存儲分為順序存儲和非順序存儲。
順序存儲也稱為向量存儲或一維數組存儲。特點是邏輯關系上相鄰的兩個元素在物理位置上也相鄰,隨機存取元素簡單,插入刪除會造成大量數據移動。線性表的順序存儲的情況下插入和刪除算法的時間復雜度為o(n);求表長以及取第i個元素的時間復雜度為o(1);
-
線性表的鏈式存儲,不要求邏輯上相鄰的數據元素在物理位置上也相鄰。由于不要求物理位置上也相鄰,那么每個節點對象包含兩個元素,一個是當前值,一個是指向下一個節點的地址。如果插入某個值,那么將插入的值的節點指向之前的后繼,之前的指向下一個節點指向這個插入的節點即可。
- 尾插入法。需要一個head指針指向頭部,一個tail指針指向尾部,其他的地址都保存在前趨的next中。
- 頭插入法。不需要tail指針指向尾部,只是需要不斷修改head頭指針。
鏈式存儲的查找比較麻煩,要按照順序一個一個查,求表長也如此。單向鏈表,頭節點至關重要。循環鏈式存儲,是指最后一個元素的next指向頭部。這樣以來,就可以查找每個元素的前趨。
雙向鏈表是在每個節點的值和next兩個元素的基礎上,再加一個prior,用來保存指向前趨的指針。當然還有雙向循環鏈表。
三:棧和隊列
兩種特殊的線性表。
棧:先進后出。限制只有棧頂才可以操作。每次pop刪除的總是最新元素,每次push壓入的元素也總是最新元素。
實現棧的方式:順序棧和鏈式棧
順序棧:入棧是在線性表的頭部插入元素,出棧是在線性表的頭部刪除一個元素。這樣效率不高,時間代價為o(n)。如果是在線性表的尾部作為棧頂,插入刪除元素,那么時間代價為o(1),效率高。
鏈式棧:單向鏈表存儲棧。操作一般在頭部。
順序棧和鏈式棧比較:當需要堆棧共享時,順序棧可以使用一個數組存儲兩個棧, 數組的兩端作為兩個棧各自的棧低,中間部分為共享區域。這樣的情況適合兩個棧有相反的需求時,此消彼長的情況。鏈式棧是每個節點多了一個指針域的開銷。
隊列:先進先出。只需要操作線性表的兩端。一端只能進入,另一端只能出。隊尾進,隊首出。有順序存儲和鏈式存儲兩種。隊列假溢出是因為隊首指針確定導致的,就是被刪除的元素空間無法被重復利用。可以讓隊首和隊尾的指針循環起來就可以,就是將元素存儲在循環向量中。
基本運算:
判斷隊空
隊列初始化
判斷隊滿
入隊元素
出隊元素
取隊首元素
順序隊列:必須用一個向量空間來存放元素。設置front和rear分別來指示隊首和隊尾的位置。
循環隊列:就是將元素存儲在循環向量中。
鏈式隊列:
四:數組和廣義表
矩陣存儲分為行優先和列優先兩種。
矩陣壓縮存儲,是針對特殊矩陣隊存儲,只存儲其元素一部分,另一部分通過相應的算法計算出來。這樣的矩陣包括對稱矩陣,稀疏矩陣和三角矩陣。
- 稀疏矩陣:矩陣中有多數為零的值。到底這個數占了全部數的多少位稀疏矩陣呢?假設有m行n列矩陣,有t個非零元素,那么滿足
(t+1)*3<=m*n
即可
廣義表是線性表的擴展。元素包括
- 原子元素
- 可以再分的元素
如果所有元素都是原子元素則是線性表。如果有可以再分的元素,也就是子表,則是廣義表。廣義表含有元素的個數稱為廣義表的長度,廣義表中含有括號對數稱為廣義表的深度,也就是層。
五:樹
非線性結構。樹的遞歸定義:樹是由根節點和若干棵子樹構成的。
一個節點的子樹個數稱為該節點的度。
度為零的節點稱為葉子或終端節點。不為零的為分支節點。除根節點之外的稱為內部節點。
一棵樹中節點度最大的值稱為該樹的度。
二叉樹:或者為空,或者由一個根節點加上兩棵左右互不交叉的子樹構成。
- 滿二叉樹:每個父親都有兩個兒子。
- 完全二叉樹
二叉樹的順序存儲:只存儲節點的值,不存儲節點之間的邏輯關系
二叉樹的鏈接存儲:每個節點由數據域和指針域兩部分組成。指針域有兩個,一個指向父親,一個指向兒子。
二叉樹遍歷,包括前中后三序遍歷,以及層次遍歷。
當我們遍歷完二叉樹,就形成一個線性序列,于是就有了唯一的前趨和后繼節點。
線索二叉樹,就是為了解決尋找前趨和后繼的。每個鏈接節點有五個變量,通常樹都是鏈式存儲。
將一棵樹轉為二叉樹的方法:
- 樹中所有相鄰兄弟之間加一條連線。
- 對樹中每個節點,只保留它與第一個兒子節點之間的聯系,刪除它與其他兒子的連線。
- 以樹的根節點為軸旋轉。
-
這樣的旋轉可以證明是唯一的。而且過程是可逆的。
將二叉樹還原為普通樹的方法:
樹的遍歷分為先根遍歷和后根遍歷。
森林的遍歷分為前序遍歷和中序遍歷。
哈夫曼樹,也是二叉樹。這種二叉樹的帶權路徑長度最小。并且每個權值都是葉子節點。
帶權路徑長度為根節點到該節點之間的路徑長度與該節點的值的乘積。該節點的值,是我們人為指定的。
路徑長度是層數減1.根節點為第一層。
六:圖
圖是非線性結構。圖中任何兩個頂點都可能有關聯,頂點間的關系是多對多點關系。圖的每個節點有任意多個前趨和后繼。圖分為有向圖和無向圖。帶權的圖稱為網。網分為有向網和無向網。
1:圖的存儲結構
圖的鄰接矩陣表示法和鄰接表表示法。
鄰接矩陣,行與列分別表示各個頂點。1表示有邊,0表示沒有邊。比如第一行第二列是1,則表示頂點1到頂點2有邊。第三行第四列是1,則表示頂點3到頂點4有邊。這是有向圖。如果是無向圖的話,那么矩陣是對稱的,就是說如果第三行第四列是1,那么第四列第三行也是1,因為頂點3和頂點4的邊沒有方向。
鄰接表:是圖的一種鏈式存儲結構。先建立一個鏈表存儲每個頂點。每個頂點有兩個域,鄰接點域和指針域。鄰接點域存序號,指針域指向邊的表節點。邊表是存儲頂點與鄰接點具有邊關系的表,每個節點也有兩個域,指針域指向下一個與鄰接表節點具有邊關系的頂點。比如頂點1與頂點2,3都有邊。那么頂點1在鄰接表中的指針域指向邊表的頂點2的位置。邊表中頂點2的指針域指向與頂點1有邊的頂點3的位置。
2:圖的遍歷
圖的深度優先遍歷和廣度優先遍歷
深度優先遍歷:遞歸訪問頂點,直到某個頂點沒有未被訪問的頂點為止,開始訪問它的前趨。如果前趨是最開始訪問的那個頂點,就結束遍歷。
廣度優先遍歷:從最開始的訪問點出發,訪問與它鄰接的所有點,再從這些鄰接點出發訪問它們的鄰接點。直到所有頂點均被訪問為止。
3:最小生成樹
克魯斯卡爾算法。普里姆算法
4:最短路徑和拓撲排序
最短路徑:迪卡斯特拉算法
拓撲排序:從網中旋轉一個入度為0的頂點并輸出。也就是沒有其它頂點指向這個頂點,只有這個頂點指向其它頂點。從網中刪除此頂點及其所有出邊。如此循環。拓撲排序解決的是各個頂點的依賴關系的有序數列。
七:排序
排序的穩定性是根據需要排序的元素中的關鍵字如果有相等的情況,那么這些相等關鍵字的元素如果排序前后的相對位置不變就是穩定的,如果這些相等關鍵字的元素相對位置發生了改變,就是不穩定的。
排序過程中是否涉及數據的內外存交換可以將排序分為:
-
內部排序
- 插入排序。將待排序的數組中元素,一個一個地插入到有序數組當中。
- 直接插入排序。穩定。正序是o(n),反序和隨機是o(n*n);
- 希爾排序。不穩定。在直接插入排序的基礎上進行分組插入。
- 選擇排序。每一趟從待排序的記錄中選擇關鍵字最小的紀錄順序放在排好序的子文件最后。
- 直接選擇排序。不穩定。o(n*n)
- 堆排序。
- 交換排序。兩兩比較待排序記錄的關鍵字,發現兩個紀錄的次序相反時就進行交換。
- 冒泡排序。正序是o(n)。最壞是o(n*n)。穩定。排序過程中交替改變掃描方向,改進不對稱性。
- 快速排序。不穩定。平均時間復雜度o(nlgn)
- 歸并排序
- 分配排序
- 插入排序。將待排序的數組中元素,一個一個地插入到有序數組當中。
-
外部排序
- 合并排序
- 直接合并排序法
八:查找
1:順序查找。適用于線性表的順序結構,也適用于線性表的鏈式存儲結構。但是鏈式存儲結構需要從第一個節點開始掃描。
2:二分查找。屬于靜態查找,因為如果該表要是還需要修改,那么就很費時。要求線性表是有序的,并且要用向量作為表的存儲結構。二分查找不會超過樹的深度,但是由于需要有序,因此也費時。二分查找只能用于順序結構。鏈式結構需要用順序查找。因為二分查找需要線性表可以隨機存取。因為二分查找需要隨機讀取一半的位置,一半的一半的位置。。。
3:分塊查找。比如給一個學號要在一個學校中查找。我們需要維護一個數組用來存放有序的的班級信息。通過二分查找找到班級所在的塊之后,再通過順序查找來查找班級中的學號。分塊查找是順序查找和二分查找的結合。需要多維護一個有序索引數組。
4:二叉排序樹。動態查找表效率高。每個節點的左邊子元素的值小于該節點,右子元素的值大于該節點。二叉排序樹最壞的情況是形成一個單支樹,最好的情況是勻稱。
5:B樹,用來對磁盤等外部存儲進行查找。B樹幾乎替代了除散列方法意外的所有大型文件查找。
6:散列表。建立關鍵字與地址之間的關系,通過對元素直接尋址來查找。兩個不同的關鍵字,由于散列函數值不同,而被映射到同一個低智商,稱為沖突。
九:動態存儲管理
動態內存分區常用算法:
最先適配法(nrst-fit):按分區在內存的先后次序從頭查找,找到符合要求的第一個分區進行分配。該算法的分配和釋放的時間性能較好,較大的空閑分區可以被保留在內存高端。但隨著低端分區不斷劃分會產生較多小分區,每次分配時查找時間開銷便會增大。
下次適配法(循環首次適應算法 next fit):按分區在內存的先后次序,從上次分配的分區起查找(到最后{區時再從頭開始},找到符合要求的第一個分區進行分配。該算法的分配和釋放的時間性能較好,使空閑分區分布得更均勻,但較大空閑分區不易保留。
最佳適配法(best-fit):按分區在內存的先后次序從頭查找,找到其大小與要求相差最小的空閑分區進行分配。從個別來看,外碎片較小;但從整體來看,會形成較多外碎片優點是較大的空閑分區可以被保留。
最壞適配法(worst- fit):按分區在內存的先后次序從頭查找,找到最大的空閑分區進行分配。基本不留下小空閑分區,不易形成外碎片。但由于較大的空閑分區不被保留,當對內存需求較大的進程需要運行時,其要求不易被滿足。