《大話資料結構》總結

weixin_33860553發表於2017-11-11
7178691-de61ee6bfad837c8.jpg

第一章 緒論

什麼是資料結構?

資料結構的定義:資料結構是相互之間存在一種或多種特定關係的資料元素的集合。


7178691-6c91f07441384363.png

第二章 演算法

演算法的特性:有窮性、確定性、可行性、輸入、輸出。

什麼是好的演算法? ----正確性、可讀性、健壯性、時間效率高、儲存量低

函式的漸近增長:給定兩個函式f(n)和g(n),如果存在一個整數N,使得對於所有的n>N,f(n)總是比g(n)大,那麼,我們說f(n)的增長漸近快於g(n)。於是我們可以得出一個結論,判斷一個演算法好不好,我們只通過少量的資料是不能做出準確判斷的,如果我們可以對比演算法的關鍵執行次數函式的漸近增長性,基本就可以分析出:某個演算法,隨著n的變大,它會越來越優於另一演算法,或者越來越差於另一演算法。

時間複雜度(大O階)的計算方法——如果級數展開學得好的話就很好理解

  • 用常數1取代執行時間中的所有加法常數。
  • 在修改後的執行次數函式中,只保留最高階項。
  • 如果最高階項存在且不是1,則去除與這個項相乘的常數。

O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n)< O(n!) < O(n^n)

第三章 線性表

線性表是零個或多個具有相同型別的資料元素的有限序列。

線性表的兩種儲存結構:順序儲存結構和鏈式儲存結構

7178691-c5e66feb8f5dd166.png

鏈式儲存結構包括四種:
單連結串列:指標的指向只是從表頭到表尾

靜態連結串列:陣列的元素都是由兩個資料域組成,data和cur。也就是說,陣列的每個下標都對應一個data和一個cur。資料域data,用來存放資料元素,也就是通常我們要處理的資料;而cur相當於單連結串列中的next指標,存放該元素的後繼在陣列中的下標,我們把cur叫做遊標。

迴圈連結串列:表尾的指標指向表頭

雙向連結串列:不僅有指向後面的指標,還有指向前面的指標

第四章 棧和佇列

棧stack是限定在表尾進行插入和刪除操作的線性表

佇列queue是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表。

棧和佇列用線性表的順序儲存結構的缺點:浪費空間 操作複雜

解決方法:對於棧來說,如果是兩個相同資料型別的棧,則可以用陣列的兩端作棧底的方法來讓兩個棧共享資料,這就可以最大化地利用陣列的空間;對於佇列來說,為了避免陣列插入和刪除時需要移動資料,於是就引入了迴圈佇列,使得隊頭和隊尾可以在陣列中迴圈變化。解決了移動資料的時間損耗,使得本來插入和刪除是O(n)的時間複雜度變成了O(1)。

逆波蘭表示式是什麼?——又叫做字尾表示式,處理四則運算,計算方法採用棧的先進後出的方式。

第五章 字串

KMP演算法是什麼?
介紹next[j]值的計算方法?

第六章 樹

樹(Tree)是n(n≥0)個結點的有限集。

線性結構和樹結構的比較:


7178691-c0f6c17ae7145644.png

樹的度是什麼?各個結點度的最大值

樹的儲存結構?雙親表示法、孩子表示法、孩子兄弟表示法(其實所有的表示法都是在指標域上做手腳)

二叉樹的分類?——斜樹、滿二叉樹、完全二叉樹(若按層序編號後其編號與同樣深度的滿二叉樹中編號結點在二叉樹中位置完全相同,那它就是完全二叉樹。)

二叉樹的性質?——
性質1:在二叉樹的第i層至多有2^(i-1)個結點(i>=1)
性質2 :深度為k的二叉樹至多有2^k-1個結點(k>=1)
性質3:對任何一棵二叉樹T,如果其終端結點數為n0,度為2的結點數為n2,則n0=n2+1。
性質4:具有n個結點的完全二叉樹的深度為|log2(n)+1|(|x|表示不大於x的最大整數)。
性質5:如果對一棵有n個結點的完全二叉樹(其深度為k)的結點按層序編號(從第1層到第層,每層從左到右),對任一結點i(1≤i≤n)有: 1.如果i=1,則結點i是二叉樹的根,無雙親;如果i>1,則其雙親是結點。 2.如果2i>n,則結點i無左孩子(結點i為葉子結點);否則其左孩子是結點2i。 3.如果2i+1>n,則結點i無右孩子;否則其右孩子是結點2i+1。

二叉樹的順序儲存結構?完全二叉樹使用順序儲存是最好的,不會浪費儲存空間;
對於一般的二叉樹用二叉連結串列,結點的結構是 lchild data rchild(中間是資料域,兩邊是指標域)

二叉樹遍歷原理(限制從左到右):

1.前序遍歷:若二叉樹為空,則空操作返回,否則先訪問根結點,然後前序遍歷左子樹,再前序遍歷右子樹。


7178691-f61f5196e2fd7a77.png

2.中序遍歷:若樹為空,則空操作返回,否則從最左下結點開始(注意並不是先訪問根結點),中序遍歷根結點的左子樹,然後是訪問根結點,最後中序遍歷右子樹。


7178691-02e28eaeedfcc187.png

3.後序遍歷:若樹為空,則空操作返回,否則從左到右先葉子後結點的方式遍歷訪問左右子樹,最後是訪問根結點。


7178691-828924a25493a9c5.png

4.層序遍歷:若樹為空,則空操作返回,否則從樹的第一層,也就是根結點開始訪問,從上而下逐層遍歷,在同一層中,按從左到右的順序對結點逐個訪問。


7178691-b40fef0f8efaa1cd.png

二叉樹遍歷的性質:
• 已知前序遍歷序列和中序遍歷序列,可以唯一確定一棵二叉樹。
• 已知後序遍歷序列和中序遍歷序列,可以唯一確定一棵二叉樹。
• 注意,已知前序和後序遍歷,是不能確定一棵二叉樹的。

什麼是線索二叉樹?——指向前驅和後繼的指標稱為線索,加上線索的二叉連結串列稱為線索連結串列,相應的二叉樹就稱為線索二叉樹。其實線索二叉樹,等於是把一棵二叉樹轉變成了一個雙向連結串列,這樣對我們的插入刪除結點、查詢某個結點都帶來了方便。所以我們對二叉樹以某種次序遍歷使其變為線索二叉樹的過程稱做是線索化。

將樹轉換為二叉樹的步驟如下 1.加線。在所有兄弟結點之間加一條連線。 2.去線。對樹中每個結點,只保留它與第一個孩子結點的連線,刪除它與其他孩子結點之間的連線。 3.層次調整。以樹的根結點為軸心,將整棵樹順時針旋轉一定的角度,使之結構層次分明。注意第一個孩子是二叉樹結點的左孩子,兄弟轉換過來的孩子是結點的右孩子。(兄弟變兒子哈哈!)

7178691-92f2346d34b2c86c.png

森林轉化為二叉樹:
森林是由若干棵樹組成的,所以完全可以理解為,森林中的每一棵樹都是兄弟,可以按照兄弟的處理辦法來操作。步驟如下: 1.把每個樹轉換為二叉樹。 2.第一棵二叉樹不動,從第二棵二叉樹開始,依次把後一棵二叉樹的根結點作為前一棵二叉樹的根結點的右孩子,用線連線起來。當所有的二叉樹連線起來後就得到了由森林轉換來的二叉樹。


7178691-e5abfad12f7f654b.png

二叉樹轉換為樹
二叉樹轉換為樹是樹轉換為二叉樹的逆過程。步驟如下: 1.加線。若某結點的左孩子結點存在,則將這個左孩子的右孩子結點、右孩子的右孩子結點、右孩子的右孩子的右孩子結點……哈,反正就是左孩子的n個右孩子結點都作為此結點的孩子。將該結點與這些右孩子結點用線連線起來。 2.去線。刪除原二叉樹中所有結點與其右孩子結點的連線。 3.層次調整。使之結構層次分明。


7178691-26f4a59ad972692d.png

判斷一棵二叉樹能夠轉換成一棵樹還是森林,標準很簡單,那就是隻要看這棵二叉樹的根結點有沒有右孩子,有就是森林,沒有就是一棵樹。

二叉樹轉換成森林,步驟如下: 1.從根結點開始,若右孩子存在,則把與右孩子結點的連線刪除,再檢視分離後的二叉樹,若右孩子存在,則連線刪除……,直到所有右孩子連線都刪除為止,得到分離的二叉樹。 2.再將每棵分離後的二叉樹轉換為樹即可。


7178691-2df9557aaf4c933f.png

樹的遍歷分為兩種方式。 1.一種是先根遍歷樹,即先訪問樹的根結點,然後依次先根遍歷根的每棵子樹。 2.另一種是後根遍歷,即先依次後根遍歷每棵子樹,然後再訪問根結點。

森林的遍歷也分為兩種方式: 1.前序遍歷:先訪問森林中第一棵樹的根結點,然後再依次先根遍歷根的每棵子樹,再依次用同樣方式遍歷除去第一棵樹的剩餘樹構成的森林。2.後序遍歷:是先訪問森林中第一棵樹,後根遍歷的方式遍歷每棵子樹,然後再訪問根結點,再依次同樣方式遍歷除去第一棵樹的剩餘樹構成的森林。

當以二叉連結串列作樹的儲存結構時,樹的先根遍歷和後根遍歷完全可以借用二叉樹的前序遍歷和中序遍歷的演算法來實現。

Huffman樹
樹的路徑長度就是從樹根到每一結點的路徑長度之和。

如果考慮到帶權的結點,結點的帶權的路徑長度為從該結點到樹根之間的路徑長度與結點上權的乘積。樹的帶權路徑長度為樹中所有葉子結點的帶權路徑長度之和。

假設有n個權值{w1,w2,...,wn},構造一棵有n個葉子結點的二叉樹,每個葉子結點帶權wk,每個葉子的路徑長度為lk,我們通常記作,則其中帶權路徑長度WPL最小的二叉樹稱做赫夫曼樹。

構造赫夫曼樹的赫夫曼演算法描述:
1.根據給定的n個權值{w1,w2,...,wn}構成n棵二叉樹的集合F={T1,T2,...,Tn},其中每棵二叉樹Ti中只有一個帶權為wi根結點,其左右子樹均為空。
2.在F中選取兩棵根結點的權值最小的樹作為左右子樹構造一棵新的二叉樹,且置新的二叉樹的根結點的權值為其左右子樹上根結點的權值之和。
3.在F中刪除這兩棵樹,同時將新得到的二叉樹加入F中。
4.重複2和3步驟,直到F只含一棵樹為止。這棵樹便是赫夫曼樹。

赫夫曼編碼是什麼?——設需要編碼的字符集為{d1,d2,...,dn},各個字元在電文中出現的次數或頻率集合為{w1,w2,...,wn},以d1,d2,...,dn作為葉子結點,以w1,w2,...,wn作為相應葉子結點的權值來構造一棵赫夫曼樹。規定赫夫曼樹的左分支代表0,右分支代表1,則從根結點到葉子結點所經過的路徑分支組成的0和1的序列便為該結點對應字元的編碼,這就是赫夫曼編碼。

第七章 圖

圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:G(V,E),其中,G表示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合。

無向邊:若頂點vi到vj之間的邊沒有方向,則稱這條邊為無向邊(Edge),用無序偶對(vi,vj)來表示。如果圖中任意兩個頂點之間的邊都是無向邊,則稱該圖為無向圖(Undirected graphs)。

有向邊:若從頂點vi到vj的邊有方向,則稱這條邊為有向邊,也稱為弧(Arc)。用有序偶<vi,vj>來表示,vi稱為弧尾(Tail),vj稱為弧頭(Head)。如果圖中任意兩個頂點之間的邊都是有向邊,則稱該圖為有向圖(Directed graphs)。

簡單圖——在圖中,若不存在頂點到其自身的邊,且同一條邊不重複出現,則稱這樣的圖為簡單圖。

在無向圖中,如果任意兩個頂點之間都存在邊,則稱該圖為無向完全圖。含有n個頂點的無向完全圖有n(n-1)/2條邊。

在有向圖中,如果任意兩個頂點之間都存在方向互為相反的兩條弧,則稱該圖為有向完全圖。含有n個頂點的有向完全圖有n×(n-1)條邊。

對於具有n個頂點和e條邊數的圖,無向圖0≤e≤n(n-1)/2,有向圖0≤e≤n(n-1)。

有很少條邊或弧的圖稱為稀疏圖,反之稱為稠密圖。

有些圖的邊或弧具有與它相關的數字,這種與圖的邊或弧相關的數叫做權(Weight)。這些權可以表示從一個頂點到另一個頂點的距離或耗費。這種帶權的圖通常稱為網(Network)。

假設有兩個圖G=(V,{E})和G'=(V',{E'}),如果V' ⊂V且E' ⊂E,則稱G'為G的子圖(Sub-graph)。

對於無向圖G=(V,{E}),如果邊(v,v')∈E,則稱頂點v和v'互為鄰接點(Adjacent),即v和v'相鄰接。邊(v,v')依附(incident)於頂點v和v',或者說(v,v')與頂點v和v'相關聯。頂點v的度(Degree)是和v相關聯的邊的數目,記為TD(v)。

對於有向圖G=(V,{E}),如果弧<v,v'>∈E,則稱頂點v鄰接到頂點v',頂點v'鄰接自頂點v。弧<v,v'>和頂點v,v'相關聯。以頂點v為頭的弧的數目稱為v的入度(InDegree),記為ID(v);以v為尾的弧的數目稱為v的出度(OutDegree),記為OD(v);頂點v的度為D(v)=ID(v)+OD(v)。

路徑的長度是路徑上的邊或弧的數目。

第一個頂點和最後一個頂點相同的路徑稱為迴路或環(Cycle)。序列中頂點不重複出現的路徑稱為簡單路徑。除了第一個頂點和最後一個頂點之外,其餘頂點不重複出現的迴路,稱為簡單迴路或簡單環。

在無向圖G中,如果從頂點v到頂點v'有路徑,則稱v和v'是連通的。如果對於圖中任意兩個頂點vi、vj∈V,vi和vj都是連通的,則稱G是連通圖(Connected Graph)。

無向圖中的極大連通子圖稱為連通分量。注意連通分量的概念,它強調: 要是子圖; 子圖要是連通的; 連通子圖含有極大頂點數; 具有極大頂點數的連通子圖包含依附於這些頂點的所有邊。

在有向圖G中,如果對於每一對vi、vj∈V、vi≠vj,從vi到vj和從vj到vi都存在路徑,則稱G是強連通圖。有向圖中的極大強連通子圖稱做有向圖的強連通分量。

所謂的一個連通圖的生成樹是一個極小的連通子圖,它含有圖中全部的n個頂點,但只有足以構成一棵樹的n-1條邊。如果一個圖有n個頂點和小於n-1條邊,則是非連通圖,如果它多於n-1邊條,必定構成一個環,因為這條邊使得它依附的那兩個頂點之間有了第二條路徑。

如果一個有向圖恰有一個頂點的入度為0,其餘頂點的入度均為1,則是一個有向樹。一個有向圖的生成森林由若干棵有向樹組成,含有圖中全部頂點,但只有足以構成若干棵不相交的有向樹的弧。

(概念太多總結一下:圖按照有無方向分為無向圖和有向圖。無向圖由頂點和邊構成,有向圖由頂點和弧構成。弧有弧尾和弧頭之分。
圖按照邊或弧的多少分稀疏圖和稠密圖。如果任意兩個頂點之間都存在邊叫完全圖,有向的叫有向完全圖。若無重複的邊或頂點到自身的邊則叫簡單圖。
圖中頂點之間有鄰接點、依附的概念。無向圖頂點的邊數叫做度,有向圖頂點分為入度和出度。
圖上的邊或弧上帶權則稱為網。
圖中頂點間存在路徑,兩頂點存在路徑則說明是連通的,如果路徑最終回到起始點則稱為環,當中不重複叫簡單路徑。若任意兩頂點都是連通的,則圖就是連通圖,有向則稱強連通圖。圖中有子圖,若子圖極大連通則就是連通分量,有向的則稱強連通分量。
無向圖中連通且n個頂點n-1條邊叫生成樹。有向圖中一頂點入度為0其餘頂點入度為1的叫有向樹。一個有向圖由若干棵有向樹構成生成森林。)

5種圖的多重連結串列儲存:
一、鄰接矩陣:圖的鄰接矩陣(Adjacency Matrix)儲存方式是用兩個陣列來表示圖。一個一維陣列vertex[m]儲存圖中頂點資訊,一個二維陣列arc[i][j](稱為鄰接矩陣)儲存圖中的邊或弧的資訊。
設圖G有n個頂點,則鄰接矩陣是一個n×n的方陣,定義為:


7178691-0fd1bbefdac1222a.png

這是一個對稱矩陣,有了這個矩陣,我們就可以很容易地知道圖中的資訊。
1.我們要判定任意兩頂點是否有邊無邊就非常容易了。
2.我們要知道某個頂點的度,其實就是這個頂點vi在鄰接矩陣中第i行(或第i列)的元素之和。
3.求頂點vi的所有鄰接點就是將矩陣中第i行元素掃描一遍,arc[i][j]為1就是鄰接點。
二、鄰接表
鄰接矩陣在處理稀疏圖時會浪費儲存空間,鄰接表是其改進。
鄰接表的處理辦法:
1.圖中頂點用一個一維陣列儲存,當然,頂點也可以用單連結串列來儲存,不過陣列可以較容易地讀取頂點資訊,更加方便。另外,對於頂點陣列中,每個資料元素還需要儲存指向第一個鄰接點的指標,以便於查詢該頂點的邊資訊。
2.圖中每個頂點vi的所有鄰接點構成一個線性表,由於鄰接點的個數不定,所以用單連結串列儲存,無向圖稱為頂點vi的邊表,有向圖則稱為頂點vi作為弧尾的出邊表。
三、十字連結串列
對於有向圖,鄰接表是有缺陷的。關心了出度問題,想了解入度就必須要遍歷整個圖才能知道,反之,逆鄰接表解決了入度卻不瞭解出度的情況。可以把鄰接表和逆鄰接表做在一起,成為十字連結串列。
重新定義頂點表結點結構為data firstin firstout
重新定義的邊表結點結構為 tailvex headvex headlink taillink
其中tailvex是指弧起點在頂點表的下標,headvex是指弧終點在頂點表中的下標,headlink是指入邊表指標域,指向終點相同的下一條邊,taillink是指邊表指標域,指向起點相同的下一條邊。如果是網,還可以再增加一個weight域來儲存權值。
四、鄰接多重表
重新定義邊表結點結構為:ivex ilink jvex jlink
其中ivex和jvex是與某條邊依附的兩個頂點在頂點表中的下標。ilink指向依附頂點ivex的下一條邊,jlink指向依附頂點jvex的下一條邊。這就是鄰接多重表結構。
鄰接多重表與鄰接表的差別,僅僅是在於同一條邊在鄰接表中用兩個結點表示,而在鄰接多重表中只有一個結點。
五、邊集陣列
邊集陣列是由兩個一維陣列構成。一個是儲存頂點的資訊;另一個是儲存邊的資訊,這個邊陣列每個資料元素由一條邊的起點下標(begin)、終點下標(end)和權(weight)組成。
邊集陣列關注的是邊的集合,在邊集陣列中要查詢一個頂點的度需要掃描整個邊陣列,效率並不高。因此它更適合對邊依次進行處理的操作,而不適合對頂點相關的操作。
圖的遍歷:
深度優先遍歷 廣度優先遍歷
深度優先遍歷類似樹的前序遍歷,圖的廣度優先遍歷就類似於樹的層序遍歷。

找連通網的最小生成樹,經典的有兩種演算法,普里姆演算法和克魯斯卡爾演算法。
普里姆Prim演算法:
假設N=(V,{E})是連通網,TE是N上最小生成樹中邊的集合。演算法從U={u0}(u0∈V),TE={}開始。重複執行下述操作:在所有u∈U,v∈V-U的邊(u,v)∈E中找一條代價最小的邊(u0,v0)併入集合TE,同時v0併入U,直至U=V為止。此時TE中必有n-1條邊,則T=(V,{TE})為N的最小生成樹。
此演算法的時間複雜度為O(n2)。
(說白了,普里姆演算法是以某頂點為起點,逐步找各頂點上最小權值的邊來構建最小生成樹的。)
克魯斯卡爾(Kruskal)演算法:
假設N=(V,{E})是連通網,則令最小生成樹的初始狀態為只有n個頂點而無邊的非連通圖T={V,{}},圖中每個頂點自成一個連通分量。在E中選擇代價最小的邊,若該邊依附的頂點落在T中不同的連通分量上,則將此邊加入到T中,否則捨去此邊而選擇下一條代價最小的邊。依次類推,直至T中所有頂點都在同一連通分量上為止。
此演算法的Find函式由邊數e決定,時間複雜度為O(loge),而外面有一個for迴圈e次。所以克魯斯卡爾演算法的時間複雜度為O(eloge)。
(說白了,把邊的權值小的先佔下來當做連通分量,再試圖把他們連起來。)
對比兩個演算法,克魯斯卡爾演算法主要是針對邊來展開,邊數少時效率會非常高,所以對於稀疏圖有很大的優勢;而普里姆演算法對於稠密圖,即邊數非常多的情況會更好一些。

計算最短路徑:
迪傑斯特拉(Dijkstra)演算法——並不是一下子就求出了源點到終點的最短路徑,而是一步步求出它們之間頂點的最短路徑,過程中都是基於已經求出的最短路徑的基礎上,求得更遠頂點的最短路徑,最終得到你要的結果。
弗洛伊德(Floyd)演算法——從圖的帶權鄰接矩陣A=[a(i,j)] n×n開始,遞迴地進行n次更新,即由矩陣D(0)=A,按一個公式,構造出矩陣D(1);又用同樣地公式由D(1)構造出D(2);……;最後又用同樣的公式由D(n-1)構造出矩陣D(n)。矩陣D(n)的i行j列元素便是i號頂點到j號頂點的最短路徑長度,稱D(n)為圖的距離矩陣。
時間複雜度是O(n3)。

在一個表示工程的有向圖中,用頂點表示活動,用弧表示活動之間的優先關係,這樣的有向圖為頂點表示活動的網,我們稱為AOV網(ActivityOn Vertex Network)。
拓撲序列:設G=(V,E)是一個具有n個頂點的有向圖,V中的頂點序列v1,v2,……,vn,滿足若從頂點vi到vj有一條路徑,則在頂點序列中頂點vi必在頂點vj之前。則我們稱這樣的頂點序列為一個拓撲序列。
所謂拓撲排序,其實就是對一個有向圖構造拓撲序列的過程。構造時會有兩個結果,如果此網的全部頂點都被輸出,則說明它是不存在環(迴路)的AOV網;如果輸出頂點數少了,哪怕是少了一個,也說明這個網存在環(迴路),不是AOV網。
對AOV網進行拓撲排序的基本思路是:從AOV網中選擇一個入度為0的頂點輸出,然後刪去此頂點,並刪除以此頂點為尾的弧,繼續重複此步驟,直到輸出全部頂點或者AOV網中不存在入度為0的頂點為止。
分析整個演算法,對一個具有n個頂點e條弧的AOV網來說,掃描頂點表,將入度為0的頂點入棧的時間複雜為O(n),而之後的while迴圈中,每個頂點進一次棧,出一次棧,入度減1的操作共執行了e次,所以整個演算法的時間複雜度為O(n+e)。

在一個表示工程的帶權有向圖中,用頂點表示事件,用有向邊表示活動,用邊上的權值表示活動的持續時間,這種有向圖的邊表示活動的網,我們稱之為AOE網(Activity On Edge Net-work)。
儘管AOE網與AOV網都是用來對工程建模的,但它們還是有很大的不同,主要體現在AOV網是頂點表示活動的網,它只描述活動之間的制約關係,而AOE網是用邊表示活動的網,邊上的權值表示活動持續的時間。因此,AOE網是要建立在活動之間制約關係沒有矛盾的基礎之上,再來分析完成整個工程至少需要多少時間,或者為縮短完成工程所需時間,應當加快哪些活動等問題。
我們把路徑上各個活動所持續的時間之和稱為路徑長度,從源點到匯點具有最大長度的路徑叫關鍵路徑,在關鍵路徑上的活動叫關鍵活動。
計算關鍵路徑:
1.事件的最早發生時間etv(earliest time ofvertex):即頂點vk的最早發生時間。
2.事件的最晚發生時間ltv(latest time ofvertex):即頂點vk的最晚發生時間,也就是每個頂點對應的事件最晚需要開始的時間,超出此時間將會延誤整個工期。
3.活動的最早開工時間ete(earliest time ofedge):即弧ak的最早發生時間。
4.活動的最晚開工時間lte(latest time ofedge):即弧ak的最晚發生時間,也就是不推遲工期的最晚開工時間。

第八章 查詢

查詢表(Search Table)是由同一型別的資料元素(或記錄)構成的集合。

靜態查詢表(Static Search Table):只作查詢操作的查詢表。它的主要操作有:(1)查詢某個“特定的”資料元素是否在查詢表中。(2)檢索某個“特定的”資料元素和各種屬性。

動態查詢表(Dynamic Search Table):在查詢過程中同時插入查詢表中不存在的資料元素,或者從查詢表中刪除已經存在的某個資料元素。顯然動態查詢表的操作就是兩個:(1)查詢時插入資料元素。(2)查詢時刪除資料元素。

為了提高查詢的效率,我們需要專門為查詢操作設定資料結構,這種面向查詢操作的資料結構稱為查詢結構。

順序查詢(Sequential Search)又叫線性查詢,是最基本的查詢技術,它的查詢過程是:從表中第一個(或最後一個)記錄開始,逐個進行記錄的關鍵字和給定值比較,若某個記錄的關鍵字和給定值相等,則查詢成功,找到所查的記錄;如果直到最後一個(或第一個)記錄,其關鍵字和給定值比較都不等時,則表中沒有所查的記錄,查詢不成功。
時間複雜度為O(n)。

有序表查詢:對目標實現進行有序化
折半查詢:折半查詢(Binary Search)技術,又稱為二分查詢。它的前提是線性表中的記錄必須是關鍵碼有序(通常從小到大有序),線性表必須採用順序儲存。折半查詢的基本思想是:在有序表中,取中間記錄作為比較物件,若給定值與中間記錄的關鍵字相等,則查詢成功;若給定值小於中間記錄的關鍵字,則在中間記錄的左半區繼續查詢;若給定值大於中間記錄的關鍵字,則在中間記錄的右半區繼續查詢。不斷重複上述過程,直到查詢成功,或所有查詢區域無記錄,查詢失敗為止。時間複雜度來是O(logn)。
插值查詢(Interpolation Search)是根據要查詢的關鍵字key與查詢表中最大最小記錄的關鍵字比較後的查詢方法,其核心就在於插值的計算公式(key-a[low])/(a[high]-a[low])。時間複雜度來也是O(logn)。
斐波那契查詢演算法的核心在於: 1)當key=a[mid]時,查詢就成功; 2)當key<a[mid]時,新範圍是第low個到第mid-1個,此時範圍個數為F[k-1]-1個; 3)當key>a[mid]時,新範圍是第m+1個到第high個,此時範圍個數為F[k-2]-1個。

索引按照結構可以分為線性索引、樹形索引和多級索引。我們重點介紹三種線性索引:稠密索引、分塊索引和倒排索引。
稠密索引:是指線上性索引中,將資料集中的每個記錄對應一個索引項。
分塊索引:對於分塊有序的資料集,將每塊對應一個索引項,這種索引方法叫做分塊索引。
倒排索引 :記錄號表儲存具有相同次關鍵字的所有記錄的記錄號(可以是指向記錄的指標或者是該記錄的主關鍵字)。這樣的索引方法就是倒排索引(in-verted index)。

二叉排序樹(Binary Sort Tree),又稱為二叉查詢樹。當我們對它進行中序遍歷時,就可以得到一個有序的序列。它或者是一棵空樹,或者是具有下列性質的二叉樹。
• 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結構的值;
• 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
• 它的左、右子樹也分別為二叉排序樹。
總之,二叉排序樹是以連結的方式儲存,保持了連結儲存結構在執行插入或刪除操作時不用移動元素的優點,只要找到合適的插入和刪除位置後,僅需修改連結指標即可。插入刪除的時間效能比較好。而對於二叉排序樹的查詢,走的就是從根結點到要查詢的結點的路徑,其比較次數等於給定值的結點在二叉排序樹的層數。極端情況,最少為1次,即根結點就是要找的結點,最多也不會超過樹的深度。也就是說,二叉排序樹的查詢效能取決於二叉排序樹的形狀。可問題就在於,二叉排序樹的形狀是不確定的。
所以,進行優化的方法是讓二叉樹的左右兩邊最好平衡一下,這樣二叉樹的深度最淺,查詢最節省時間。

平衡二叉樹(Self-Balancing Binary SearchTree或Height-Balanced Binary Search Tree),是一種二叉排序樹,其中每一個節點的左子樹和右子樹的高度差至多等於1。
我們將二叉樹上結點的左子樹深度減去右子樹深度的值稱為平衡因子BF(Balance Factor),那麼平衡二叉樹上所有結點的平衡因子只可能是-1、0和1。
距離插入結點最近的,且平衡因子的絕對值大於1的結點為根的子樹,我們稱為最小不平衡子樹。

平衡二叉樹構建的基本思想就是在構建二叉排序樹的過程中,每當插入一個結點時,先檢查是否因插入而破壞了樹的平衡性,若是,則找出最小不平衡子樹。在保持二叉排序樹特性的前提下,調整最小不平衡子樹中各結點之間的連結關係,進行相應的旋轉,使之成為新的平衡子樹。

多路查詢樹(muitl-way search tree),其每一個結點的孩子數可以多於兩個,且每一個結點處可以儲存多個元素。由於它是查詢樹,所有元素之間存在某種特定的排序關係。

2-3樹是這樣的一棵多路查詢樹:其中的每一個結點都具有兩個孩子(我們稱它為2結點)或三個孩子(我們稱它為3結點)。
一個2結點包含一個元素和兩個孩子(或沒有孩子),且與二叉排序樹類似,左子樹包含的元素小於該元素,右子樹包含的元素大於該元素。不過,與二叉排序樹不同的是,這個2結點要麼沒有孩子,要有就有兩個,不能只有一個孩子。
一個3結點包含一小一大兩個元素和三個孩子(或沒有孩子),一個3結點要麼沒有孩子,要麼具有3個孩子。如果某個3結點有孩子的話,左子樹包含小於較小元素的元素,右子樹包含大於較大元素的元素,中間子樹包含介於兩元素之間的元素。
並且2-3樹中所有的葉子都在同一層次上。


7178691-7f318b4557ba5efc.png

2-3-4樹:它其實就是2-3樹的概念擴充套件,包括了4結點的使用。一個4結點包含小中大三個元素和四個孩子(或沒有孩子),一個4結點要麼沒有孩子,要麼具有4個孩子。如果某個4結點有孩子的話,左子樹包含小於最小元素的元素;第二子樹包含大於最小元素,小於第二元素的元素;第三子樹包含大於第二元素,小於最大元素的元素;右子樹包含大於最大元素的元素。
B樹:,2-3樹是3階B樹,2-3-4樹是4階B樹。
一個m階的B樹具有如下屬性:
• 如果根結點不是葉結點,則其至少有兩棵子樹。
• 每一個非根的分支結點都有k-1個元素和k個孩子,其中。每一個葉子結點n都有k-1個元素,其中。
• 所有葉子結點都位於同一層次。
• 所有分支結點包含下列資訊資料

B+樹是應檔案系統所需而出的一種B樹的變形樹,注意嚴格意義上講,它其實已經不是第六章定義的樹了。在B樹中,每一個元素在該樹中只出現一次,有可能在葉子結點上,也有可能在分支結點上。而在B+樹中,出現在分支結點中的元素會被當作它們在該分支結點位置的中序後繼者(葉子結點)中再次列出。另外,每一個葉子結點都會儲存一個指向後一葉子結點的指標。下圖是B+樹。


7178691-797fd5b8a5a0cdae.png

一棵m階的B+樹和m階的B樹的差異在於:
有n棵子樹的結點中包含有n個關鍵字;
所有的葉子結點包含全部關鍵字的資訊,及指向含這些關鍵字記錄的指標,葉子結點本身依關鍵字的大小自小而大順序連結;
所有分支結點可以看成是索引,結點中僅含有其子樹中的最大(或最小)關鍵字。

雜湊表查詢(雜湊表)
雜湊技術是在記錄的儲存位置和它的關鍵字之間建立一個確定的對應關係f,使得每個關鍵字key對應一個儲存位置f(key)。查詢時,根據這個確定的對應關係找到給定值key的對映f(key),若查詢集合中存在這個記錄,則必定在f(key)的位置上。我們把這種對應關係f稱為雜湊函式,又稱為雜湊(Hash)函式。

採用雜湊技術將記錄儲存在一塊連續的儲存空間中,這塊連續儲存空間稱為雜湊表或雜湊表(Hash table)。

設計好的雜湊函式:1計算簡單 2雜湊地址分佈均勻。方法有:
直接地址法:取關鍵字的某個線性函式值為雜湊地址
數字分析法:使用關鍵字的一部分來計算雜湊儲存位置的方法

平方取中法:
摺疊法:摺疊法是將關鍵字從左到右分割成位數相等的幾部分(注意最後一部分位數不夠時可以短些),然後將這幾部分疊加求和,並按雜湊表表長,取後幾位作為雜湊地址。
除留餘數法:
隨機數法:選擇一個隨機數,取關鍵字的隨機函式值為它的雜湊地址。也就是f(key)=random(key)。這裡random是隨機函式。當關鍵字的長度不等時,採用這個方法構造雜湊函式是比較合適的。

處理三列衝突的方法:
開放定址法:所謂的開放定址法就是一旦發生了衝突,就去尋找下一個空的雜湊地址,只要雜湊表足夠大,空的雜湊地址總能找到,並將記錄存入。
再雜湊函式法:使用多個雜湊函式,如果發生衝突,則換一個雜湊函式。
鏈地址法:將所有關鍵字為同義詞的結點連結在同一個單連結串列中。若選定的雜湊表長度為m,則可將雜湊表定義為一個由m個頭指標組成的指標陣列T[0..m-1]。
公共溢位區法:為所有衝突的單列出一個區域

雜湊查詢的平均查詢長度取決於哪些因素?1.雜湊函式是否均勻 2.處理衝突的方法 3.雜湊表的裝填因子

第九章 排序

假設ki=kj(1≤i≤n,1≤j≤n,i≠j),且在排序前的序列中ri領先於rj(即i<j)。如果排序後ri仍領先於rj,則稱所用的排序方法是穩定的;反之,若可能使得排序後的序列中rj領先ri,則稱所用的排序方法是不穩定的。

內排序是在排序整個過程中,待排序的所有記錄全部被放置在記憶體中。外排序是由於排序的記錄個數太多,不能同時放置在記憶體,整個排序過程需要在內外存之間多次交換資料才能進行。

排序演算法的效能主要受3個方面的影響:時間效能、輔助空間、演算法的複雜性。按照演算法的複雜度分為兩大類,氣泡排序、簡單選擇排序和直接插入排序屬於簡單演算法,而希爾排序、堆排序、歸併排序、快速排序屬於改進演算法。

氣泡排序(Bubble Sort)一種交換排序,它的基本思想是:兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄為止。

簡單選擇排序法(Simple Selection Sort)就是通過n-i次關鍵字間的比較,從n-i+1個記錄中選出關鍵字最小的記錄,並和第i(1≤i≤n)個記錄交換之。

直接插入排序:基本操作是將一個記錄插入到已經排好序的有序表中,從而得到一個新的、記錄數增1的有序表。

希爾排序(相當於直接插入法的升級)::將相距某個“增量”的記錄組成一個子序列,這樣才能保證在子序列內分別進行直接插入排序後得到的結果是基本有序而不是區域性有序。逐漸縮小這個“增量”。

堆排序(相當於簡單選擇排序的升級):
堆是具有下列性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱為小頂堆。
堆排序(Heap Sort)就是利用堆(假設利用大頂堆)進行排序的方法。它的基本思想是,將待排序的序列構造成一個大頂堆。此時,整個序列的最大值就是堆頂的根結點。將它移走(其實就是將其與堆陣列的末尾元素交換,此時末尾元素就是最大值),然後將剩餘的n-1個序列重新構造成一個堆,這樣就會得到n個元素中的次大值。如此反覆執行,便能得到一個有序序列了。

歸併排序:歸併排序(Merging Sort)就是利用歸併的思想實現的排序方法。它的原理是假設初始序列含有n個記錄,則可以看成是n個有序的子序列,每個子序列的長度為1,然後兩兩歸併,得到|n/2|(|x|表示不小於x的最小整數)個長度為2或1的有序子序列;再兩兩歸併,……,如此重複,直至得到一個長度為n的有序序列為止,這種排序方法稱為2路歸併排序。

快速排序(排序演算法王者,20世紀十大演算法之一!卻是前面最慢的氣泡排序的升級):基本思想是通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序的目的。

總結:
歸類


7178691-1a4b1741dd5c2a31.png

時間複雜度比較:


7178691-68c5232a95c8a58b.png

從最好情況看,反而冒泡和直接插入排序要更勝一籌,也就是說,如果你的待排序序列總是基本有序,反而不應該考慮4種複雜的改進演算法。 從最壞情況看,堆排序與歸併排序又強過快速排序以及其他簡單排序。

從這三組時間複雜度的資料對比中,我們可以得出這樣一個認識。堆排序和歸併排序就像兩個參加奧數考試的優等生,心理素質強,發揮穩定。而快速排序像是很情緒化的天才,心情好時表現極佳,碰到較糟糕環境會變得差強人意。但是他們如果都來比賽計算個位數的加減法,它們反而算不過成績極普通的冒泡和直接插入。 從空間複雜度來說,歸併排序強調要馬跑得快,就得給馬吃個飽。快速排序也有相應的空間要求,反而堆排序等卻都是少量索取,大量付出,對空間要求是O(1)。如果執行演算法的軟體所處的環境非常在乎記憶體使用量的多少時,選擇歸併排序和快速排序就不是一個較好的決策了。

從穩定性來看,歸併排序獨佔鰲頭,我們前面也說過,對於非常在乎排序穩定性的應用中,歸併排序是個好演算法。 從待排序記錄的個數上來說,待排序的個數n越小,採用簡單排序方法越合適。反之,n越大,採用改進排序方法越合適。這也就是我們為什麼對快速排序優化時,增加了一個閥值,低於閥值時換作直接插入排序的原因。

因此對於資料量不是很大而記錄的關鍵字資訊量較大的排序要求,簡單排序演算法是佔優的。

相關文章