目錄
- 0.抽象數據類型ADT——Abstract Data Type
- 1.表ADT及其實現
1.1 表ADT
1.2 表的實現
??1.2.1 數組實現
??1.2.2 鏈表
??1.2.3 雙鏈表
??1.2.4 循環鏈表
1.3 表的應用
??1.3.1 多項式
??1.3.2 基數排序
??1.3.3 多重表 - 2.棧ADT及其實現
2.1 棧ADT
2.2 棧的實現
2.3 棧的應用
??2.3.1 平衡符號
??2.3.2 后綴表達式
??2.3.3 中綴到后綴的轉換
??2.3.4 函數調用 - 3.隊列ADT及其實現
3.1 隊列ADT
3.2 隊列的實現
3.3 隊列的應用
??3.3.1 排隊論
??3.3.2 任務隊列
??3.3.3 在圖論中有大量的應用
??3.3.4 消息隊列
??3.3.5 linux內核進程執行隊列(按優先級輪轉) - 4.哈希表ADT及其實現
- 5.查找樹ADT及其實現
- 6.優先隊列(堆)ADT及其實現
- 7.不相交集ADT及其實現——并查集
0.抽象數據類型ADT——Abstract Data Type
ADT是一些操作的集合。抽象數據類型是數學的抽象,在ADT的定義中不涉及如何實現操作的結合。
對諸如表、集合、圖和它們的操作一起可以看作是抽象數據類型,就像整數、實數是數據類型一樣。對于集合ADT,可以有諸如并(union)、交(intersection)、獲取大小(size)以及取余(complement)等操作。
1.表ADT及其實現
1.1 表ADT
1)表ADT的數據結構
形如A1, A2, ..., An的表。表的大小是n。大小為0的表為空表。
對除空表外的任何表,說Ai+1后繼Ai并稱Ai-1前驅Ai。表中第一個元素是A1,最后一個元素是An。不定義A1的前驅元和An的后繼元。元素Ai在表中的位置為i。
2)表ADT上進行的操作的集合
- PrintList
- MakeEmpty
- Find返回關鍵字首次出現的位置
- FindKth返回某個位置上的元素
- Insert/Delete從表的某個位置插入和刪除某個關鍵字
1.2 表的實現
1.2.1 數組實現(連續存儲)
表的大小實現需要已知(除非實現位動態數組)
插入和刪除是昂貴的,最壞情況為O(N)
1.2.2 鏈表
鏈表允許不連續存儲。
鏈表有一系列不在內存中相連的結構組成。每一個結構均含有表元素和指向包含該元素后繼元的結構的指針(Next指針)。
實現時,采用帶有表頭的鏈表:
1)鏈表的聲明和一些簡單的判斷
2)查找
3)刪除
4)插入
5)刪除整個鏈表
1.2.3 雙鏈表
1.2.4 循環鏈表
1.3 表的應用
1.3.1 多項式
使用數組(適合稠密)和鏈表(適合稀疏)實現。
1.3.2 基數排序
1.3.3 多重表
2.棧ADT及其實現
2.1 棧ADT——LIFO(后進先出)表
棧是限制插入和刪除只能在一個位置上進行的表,該位置是表的末端,叫做棧的頂。
2.2 棧的實現
任何實現表的方法都能實現棧。
2.2.1 棧的鏈表實現
通過在表頂端插入來實現push,通過刪除表頂端元素實現pop。
2.2.2 棧的數組實現
2.3 棧的應用
2.3.1 平衡符號
針對括號類的檢查:
- 做一個空棧。讀入字符直到文件尾
- 如果字符是一個開發符號,則將其推入棧中
如果字符是一個封閉符號,則當空棧時報錯;否則,將棧元素彈出。
如果彈出的符號不是對應的開放符號,則報錯。 - 在文件尾,如果棧非空則報錯。
2.3.2 后綴表達式
后綴表達式: a b c * + d e * f + g * +
- 當見到一個數是就把它推入棧中
- 遇到一個運算符時,從棧中彈出兩個數,并運用該運算符計算,并將所得結果推入棧中
2.3.3 中綴到后綴的轉換(棧的操作本質上是從左到右,將優先級高的先彈出,或者說是按照計算的次序進行彈出的)
將中綴表達式 a + b * c + (d * e + f) * g
轉成后綴表達式 a b c * + d e * f + g * +
- 當讀到一個操作數的時候,立即把它放到輸出中
- 操作符不立即輸出,將其放到棧中。當遇到做括號時,也將其推入棧中
- 見到一個右括號,將棧元素彈出,將彈出的符號寫出,直到遇到一個對應的左括號。左括號彈出,不輸出。
- (如果棧頂符號的優先級高于當前符號,要彈出)如果見到任何其他的符號: + * (,從棧中彈出棧元素直到發現優先級更低的元素位置。例外:除非在處理),否則不從棧中移走(。當從棧彈出元素的工作完成后,在將操作符壓入棧中。
- 讀到輸入的末尾,將棧元素彈出知道該棧變成空棧,將符號寫到輸出中。
a + b * c + (d * e + f) * g:
2.3.4 函數調用
函數調用是,需要存儲當前所有重要信息(活動記錄、棧幀),諸如寄存器的值和返回地址。
將這些信息放到棧中,然后控制轉移到新函數,新函數可以自由地用它的一些值代替這些寄存器。
棧溢出:許多系統中是不檢測溢出的。由于有太多同時在運行著的函數,用盡棧空間情況總是可能發生的。
當棧太大是,可能觸及到你的程序部分。
- 如果撞進了你的程序部分,產生一些無意義的指令并在這些指令執行時程序崩潰
- 如果撞進了你的數據部分,你將一些信息寫入數據時,將毀壞棧的信息,很可能是返回地址,則你的程序將返回到某個奇怪的地址,從而程序崩潰。
發生棧溢出一般是由失控遞歸(忘記基準情形)引起的。
消除遞歸一般方法是使用一個棧,而且僅當你能夠把最低限度的最小值放到棧上時,這個方法才值得一用。
3.隊列ADT及其實現
3.1 隊列ADT——FIFO表
隊列也是表,插入在一端(隊尾rear),刪除在另一端(對頭front)。
3.2 隊列的實現
同棧一樣,任何表的實現都是合法的。
3.2.1 隊列的鏈表實現
3.2.2 隊列的數組實現
3.3 隊列的應用
3.3.1 排隊論
- 當所有的接線員忙得不可開交的時候,對大公司的傳呼一般都被放到一個隊列中
- 在大規模的大學里,如果所有的終端都被占用,由于資源有限,學生們必須在一個等代表上簽字。在終端上待得時間最長的學生將首先被強制離開,而等待時間最長的學習則將是下一個被允許使用終端的用戶。
處理用概率的方法計算用戶排隊預計等待時間、等待服務的隊列能夠排多長,以及其他一些諸如此列的問題將用到被稱為排隊類的整個數學分支。
問題的答案依賴于用戶加入隊列的頻率以及一旦用戶得到服務時處理服務花費的時間。這兩個參數作為概率分布函數給出。
如果有k個接線員,直接解決比較困難,可以用模擬的方法(使用一個隊列)進行進行求解。
模擬方法:(當k變大時需要模擬)
- 模擬由處理中的事件組成。這里的兩個事件:
1)一位顧客的到達
2)一位顧客的離開,從而騰出一名出納員 - 使用過概率函數來生成一個輸入流,由每位顧客的到達時間和服務時間的序偶組成,并通過到達時間排序。不必使用一天中的準確時間,而是使用單位時間量,稱之為一個滴答(tick)
- 使用隊列進行模擬
3.3.2 任務隊列
將任務按照順序放到一個隊列中,用一個線程池(多個處理線程)輪流沖任務隊列中取出任務進行處理。
3.3.3 在圖論中有大量的應用
3.3.4 消息隊列
3.3.5 linux內核進程執行隊列(按優先級輪轉)
4.哈希表ADT及其實現
4.1 哈希表ADT
散列(hashing)是一種用于以常數平均時間執行插入、刪除、查找的技術。
那些需要元素間任何排序信息的操作將不會得到有效支持。因此諸如FindMin、FindMax等操作都是哈希表不支持的。
4.2 哈希表的實現
實現參見:http://www.lxweimin.com/p/6dfd8c4c2b50
5.查找樹ADT及其實現
參見http://www.lxweimin.com/p/bdd7442f54e2
5.1 查找樹ADT
5.2 查找樹的實現
5.2.1 二叉查找樹
5.2.2 AVL樹
5.2.3 伸展樹
5.2.4 B-樹
5.2.5 紅黑樹
參考紅黑樹專題
參考2-3-4樹及2-3樹的總結
6.優先隊列(堆)ADT及其實現
6.1 優先隊列ADT
優先隊列是允許至少下列兩種操作的數據結構:
1)Insert
2)DeleteMin 找出、返回和刪除優先隊列中最小的元素
6.2 優先隊列的實現
參見http://www.lxweimin.com/p/f62787325788
6.2.1 二叉堆
6.2.2 d-堆
6.2.3 左式堆
6.2.4 斜堆
6.2.5 二項隊列
6.2.6 斐波那契堆
6.2.7 van Emde Boas樹
7.不相交集ADT及其實現——并查集
7.1 不相交集ADT
一些應用涉及將n個元素分成一組不相交的集合。這些應用經常需要進行兩種特別的操作:尋找包含給定元素的唯一集合和合并兩個集合。
支持以下三個操作:
- MAKE-SET(x):建立一個新的集合,它的唯一成員是x。因為各個集合是不相交的,故x不會出現在別的某個集合中。
- UNION(x, y):將包含x和y的兩個動態集合(Sx和Sy)合并成一個新的集合。
- FIND-SET(x):返回一個指針,這個指針指向包含x的(唯一)結合的代表。
7.2 不相交集的實現
7.2.1 用鏈表表示集合
1)表示方法
每個集合用一個鏈表來表示。
- head指向表的第一個對象,也是集合的代表
- tail屬性指向最后一個對象
- 集合每個對象包括:集合成員、指向下一個對象的指針、指向集合對象的指針
2)操作
- MAKE-SET——O(1)時間
- FIND-SET——O(1)時間
x對象的指向集合對象的指針,然后通過集合對象返回head指向對象的成員。 -
UNION——鏈表合并,但是必須更新一個鏈表(集合)中素有對象指向集合對象的指針。最壞情況下的UNION平均攤還時間為Θ(n)。
UNION的一種方法:總是將較短的表拼接到較長的鏈表中。
7.2.2 用有根樹表示集合——不相交集合森林
1)基礎表示法
使用有根樹來表示集合,樹中每個結點包含一個成員,每棵樹代表一個集合。
在一個不相交集合森林(disjoint-set forest)中,每個成員僅指向它的父節點。
- MAKE-SET
- FIND-SET:沿著指向父節點的指針找到書的根。
這一通向根節點的簡單路徑上所訪問的結點構成了查找路徑。 - UNION:使得一棵樹的根指向另外一顆樹的根
2)按秩合并與路徑壓縮
按秩合并
對于每個結點,維護一個秩,它表示該節點高度(從該結點到某一后代葉節點的最長簡單路徑上邊的數目)的一個上界。
在使用按秩合并策略的UNION操作中,讓具有較小的秩的根指向具有較大秩的根。-
路徑壓縮
在FIND-SET操作中,使用這種策略可以使查找路徑中的每個結點直接指向根。路徑壓縮并不改變任何結點的秩。
-
MAKE-SET
-
UNION(x, y)
-
FIND-SET(x)——遞歸的精妙作用(Amazing!)
該遞歸是一種兩趟方法:
1)第一趟沿著查找路徑向上直到找到根
2)遞歸回溯時,第二趟沿著搜索向下更新每個結點,使其直接指向根。
3)運行時間分析
- n——表示MAKE-SET操作的次數,這里假設n個MAKE-SET操作總是最先執行的n個操作。
- m——表示MAKE-SET、UNION和FIND-SET操作的總次數(m >= n)
當同時使用按秩合并和路徑壓縮時,最壞情況的運行時間為O(mα(n)),這里α(n)是一個增長非常慢的函數,在任何一個可以想得到的不相交集合數據結構的應用中,都有α(n) <= 4.
詳細分析參見攤還分析:3.2.8 不相交集合——并查集
7.3 不相交集合的應用
7.3.1 確定無向圖的連通分量
7.3.2 同一個人的多個賬號
- 以前做的某個投票網站當中需要查封小號刷票的作弊賬號,就在后臺悄悄做了一個設置,如果某個人不小心帶著相同的cookies登錄了不同的賬號(也就是在同一個瀏覽器上退出一個賬號之后又登入了另一個賬號),登錄系統就會悄悄標記這兩個賬號屬于同一個人,如果A和B是同一個人,B和C也是同一個人,那么A、B、C都是同一個人,我們最后只關心哪些賬號是同一個人而哪些不是,并不關心究竟是通過怎樣的步驟發現它們是同一個人的,這就是并查集最典型的應用。
- 這個問題就可以在MySQL里面創建一個表,它有兩個字段,第一個叫做ID,主鍵,第二個叫做Parent,當前等價的父節點,這就是一個MySQL數據表表示的并查集,然后隨便用SELECT和UPDATE實現一下,并查集接近O(1)的復雜度,一般也就幾次MySQL查詢