第 1 章 數據結構緒論
程序 = 數據結構 + 算法
數據結構:是相互之間存在的一種或多種特定關系的數據元素的集合。
為編寫出一個“好”的程序,必須分析待處理對象的特性及各處理對象之間存在的關系。這也就是研究數據結構的意義所在。
按照視點的不同,可以把數據結構分為 邏輯結構 和 物理結構:
?? 邏輯結構:是指數據對象中數據元素之間的相互關系。其可具體分為以下四種關系:
??1. 集合結構:數據元素除了同屬于一個集合外,它們之間沒有其他關系。如下圖所示:
??2. 線性結構:數據元素之間是一對一關系。如下圖所示:
??3. 樹形結構:數據元素之間呈現一對多關系。如下圖所示:
??4. 圖形結構:數據元素是多對多關系。如下圖所示:
?? 物理結構:是指數據的邏輯結構在計算機中的存儲形式,因此也稱為 存儲結構。
數據是數據元素的集合,那么根據物理結構的定義,實際上就是如何把數據元素存儲到計算機的存儲器中。存儲器主要是針對內存而言的,像硬盤,軟盤,光盤等外部存儲器的數據組織通常用文件結構來描述。
數據的存儲結構應正確反映數據元素之間的邏輯關系。
數據元素的存儲結構形式有兩種:順序存儲 和 鏈式存儲:
??1. 順序存儲結構:是把數據元素存放在地址連續的存儲單元里,其數據間的邏輯關系和物理關系是一致的。如下圖所示:
??2. 鏈式存儲結構:是把數據元素存放在任意的存儲單元里,這組存儲單元可以是連續的,也可以是不連續的。如下圖所示:
- 邏輯結構是面向問題的,而物理結構是面向計算機的,其基本的目標就是將數據及其邏輯關系存儲到計算機的內存中。
第 2 章:算法
算法:算法是解決特定問題求解步驟的描述,在計算機中表現為指令的有限序列,并且每條指令表示一個或多個操作。 簡而言之,算法是描述解決問題的方法。
算法的特性:輸入、輸出、有窮性、確定性和可行性。
好的算法,應該具有正確性,可讀性,健壯性,高效率和低存儲量的特征。
算法時間效率度量:
? 可以忽略加法常數:
? 與最高次項相乘的常數可忽略:
? 最高次項的指數大的,函數隨著 n 的增長,結果也會變得增長得更快:
? 判斷一個算法的(時間)效率時,函數中常數和其他次要項常常可以忽略,而更應該關注主項(最高階項)的階數:時間復雜度:大 O 階推導方法:
?1.用常數 1 取代運行時間中的所有加法常數。
?2.在修改后的運行次數函數中,只保留最高階項。
?3.如果最高階項存在且不是 1,則去除與這個項相乘的常數(即),得到的結果就是大 O 階。
舉個例子,如下代碼,請用大 O 推導式求出函數的時間復雜度:
int i,j;
for ( i = 0; i < n; ++i){
for(j = i; j < n; ++j){
/*時間復雜度為 O(1) 的程序步驟序列 */
}
}
分析:對于外循環,其時間復雜度為 O(n);對于內循環環,當 i=0 時,內循環執行了 n 次,當 i=1 時,執行了 n-1 次,······當 i=n-1 時,執行了 1 次。因此內循環總的執行次數為:
根據大 O 階推導方法,最終上述代碼的時間復雜度為 。
-
空間復雜度:算法的空間復雜度通過計算算法所需的存儲空間實現。
一般情況下,一個程序在機器上執行時,除了需要存儲程序本身的指令,常數,變量和輸入數據外,還需要存儲對數據操作的存儲單元。若輸入數據所占空間只取決于問題本身,和算法無關,這樣就只需要分析該算法在實現時所需的輔助單元即可。若算法執行時所需的輔助空間相對于輸入數據量而言是個常數,則稱此算法為原地工作,空間復雜度為
第 3 章:線性表
線性表(List):零個或多個數據元素的有限序列。
線性表的順序存儲結構:指的是用一段地址連續的存儲單元依次存儲線性表的數據元素。
線性表()的順序存儲示意圖如下所示:
-
存儲器中的每個存儲單元都有自己的編號,這個編號稱為地址。每個數據元素,不管它是整型,實型還是字符型,它都是需要占用一定的存儲單元空間的,假設占用的是 c 個存儲單元,那么對于線性表的第 i 個數據元素
的存儲位置都可以由
推導算出:
上述推導公式具體內容如下圖所示:
通過該公式,就可以隨時算出線性表中任意位置的地址,不管是第一個還是最后一個,都是相同的時間。也即對于線性表每個位置的存入或者取出數據,對于計算機來說都是相等的時間,也就是一個常數時間,因此,線性表的存取操作時間性能為 。我們通常將存取操作具備常數性能(
)的存儲結構稱為 隨機存儲結構。
線性表的順序存儲結構,對于存取操作,其時間復雜度為
(因為元素位置可以直接計算得到);對于插入和刪除操作,其時間復雜度為
(因為插入或刪除后,需要移動其余元素)。因此,線性表順序存儲結構比較適用于元素存取操作較多,增刪操作較少的場景。
鏈表:一個或多個 結點 組合而成的數據結構稱為 鏈表。其中,結點 一般由兩部分內容構成:
? 數據域:存儲真實數據元素。
? 指針域:存儲下一個結點的地址(指針)。
- 一般把鏈表中的第一個結點稱為 頭指針,其存儲鏈表的第一個數據元素。有時,為了能更加方便地對鏈表進行操作,會在單鏈表的第一個結點(即頭指針)前附設一個結點,稱為 頭結點。頭結點的數據域可以不存儲任何信息,其指針域存儲指向第一個結點的指針(即指向頭指針)。如下圖所示:
在線性表的順序存儲結構(即數組)中,其任意一個元素的存儲位置可以通過計算得到,因此其數據讀取的時間復雜度為
;而對于單鏈表結構,假設需要獲取第 i 個元素,則必須從第一個結點開始依次進行遍歷,直到達到第 i 個結點。因此,對于單鏈表結構而言,其數據元素讀取的時間復雜度為
。
在線性表的順序存儲結構(即數組)中,對任意一個元素的增刪操作,其時間復雜度為
(因為存在移動操作,對尾元素進行增刪操作其時間復雜度為
);而對單鏈表結構來說,對其任意一個位置進行增刪操作,其時間復雜度為
(因為需要先進行遍歷找到目標元素,對頭指針的增刪操作其時間復雜度為
)。因此,如果只對一個元素進行增刪操作,兩種結構并不存在優劣之分,但如果針對多個數據進行增刪,由于線性表每一次增刪都需要移動 n-i 個元素,即每個元素的操作都為
;而單鏈表只在第一次遍歷定位目標元素時為
,對后續元素的增刪只需簡單地賦值移動指針即可,其時間復雜度為
。因此,對于插入或刪除數據越頻繁的操作,單鏈表的效率就越明顯。
將單鏈表中的終端結點的指針端由空指針改為指向頭結點,就使整個單鏈表形成一個環,這種頭尾相接的單鏈表稱為單循環鏈表,簡稱 循環鏈表(circular linked list)。
為了使空鏈表與非空鏈表處理一致,我們通常設一個頭結點(循環鏈表不一定需要頭結點)。其實循環鏈表和單鏈表的主要差異就在于循環的判斷條件上,單鏈表判斷結束的條件為尾結點是否指向空:
p->next = NULL
,而循環鏈表判定的條件為當前結點是否指向頭結點:p->next = head
,是則當前結點為尾結點。雙向鏈表(double linked list):在單鏈表的每個結點中,再設置一個指向其前驅結點的指針域。
第 4 章:棧與隊列
棧(stack):是限定僅在表尾(棧頂)進行插入和刪除操作的線性表。
我們把允許插入和刪除的一端稱為 棧頂(top),另一端稱為 棧底(bottom),不含任何任何數據元素的棧稱為 空棧。棧又稱為 后進先出(Last In First Out) 的線性表,簡稱 LIFO 結構。
棧 是線性表的特例,其具備先進后出 FILO 特性。可以使用線性表的順序存儲結構(即數組)實現棧,將之稱之為 順序棧;可以使用單鏈表結構實現棧,將之稱之為 鏈棧。兩者示意圖如下所示:
順序棧和鏈棧的時間復雜度均為
。對于空間性能,順序棧需要事先確定一個固定的長度(數組長度),可能存在內存空間浪費問題,但它的優勢是存取時定位很方便;而鏈棧則要求每個元素都要配套一個指向下個結點的指針域,增大了內存開銷,但好處是棧的長度無限。因此,如果棧的使用過程中元素變化不可預料,有時很小,有時很大,那么最好使用鏈棧;反之,如果它的變化在可控范圍內,則建議使用順序棧。
棧的內部實現原理其實就是數組或鏈表的操作,而之所以引入 棧 這個概念,是為了將程序設計問題模型化,用高層的模塊指導特定行為(棧的先進后出特性),劃分了不同關注層次,使得思考范圍縮小,更加聚焦于我們致力解決的問題核心,簡化了程序設計的問題。
棧 有一個很重要的應用:遞歸。每個遞歸定義必須至少有一個條件,使得當滿足條件時,遞歸不再進行。
遞歸 的一個經典例子為:斐波那契數列(Fibonacci),指的是這樣一個數列:1、1、2、3、5、8、13、21、……,即當前位置的值為前面兩項之和。用數學表達式表達如下:
如果我們直接將按上面的公式用代碼進行翻譯,如下所示:
int fbi(const int n){
int i;
int ret;
int last1 = 0;
int last2 = 1;
if (n == 0){
ret = 0;
}else if (n == 1){
ret = 1;
}else{
for(i = 2; i <= n; ++i){
ret = last1 + last2;
last1 = last2;
last2 = ret;
}
}
return ret;
}
但是如果我們使用遞歸方式實現,則會更加簡潔:
int fbi(const int n){
if (n < 2){
return n == 0 ? 0 : 1;
}
return fbi(n-1) + fbi(n-2);
}
使用遞歸,簡潔之余,還更加契合其數學公式的定義。
棧 的另一個常見的應用為:數學表達式的求值。其原理是通過將 中綴表達式(即標準的四則運算表達式) 以特定操作進行進棧出棧操作,得到一個對應的 后綴表達式(也稱為逆波蘭(Reverse Polish Notation,PRN)表示);然后將該后綴表達式再次通過特定操作進行出棧入棧操作,即可得到運算結果。
隊列(queue):是只允許在一端進行插入操作,而在另一端進行刪除操作的線性表。
隊列 是一種 先進先出(First In First Out) 的線性表。允許插入的一端稱為隊尾,允許刪除的一端稱為對頭。
線性表有順序存儲和鏈式存儲,棧是線性表,所以有這兩種存儲方式。同樣,隊列作為一種特殊的線性表,也同樣存在這兩種存儲方式。
第 5 章:串
串(string) 是由零個或多個字符組成的有限序列,又名叫 字符串。
串 的邏輯結構和線性表很相似,不同之處在于串針對的是字符集,也就是串中的元素都是字符。因此,對于串的基本操作與線性表是有很大差別的。線性表更關注的是單個元素的操作,比如查找一個元素,插入或刪除一個元素,但串中更多的是查找子串位置,得到指定位置子串,替換子串等操作。
串 的存儲結構與線性表相同,分為兩種:
? 串的順序存儲結構:串的順序存儲結構是用 一組地址連續的存儲單元 來存儲串中的字符序列。一般是用定長數組來定義。
由于是定長數組,因此就會存在一個預定義的最大串長度,一般可以將實際的串長度值保存在數組 0 下標位置,也可以放在數組最后一個下標位置,也有些語言使用在串值后面加一個不計入串長度的結束標記符(比如\0
)來表示串值得終結,這樣就無需使用數字進行記錄。
? 串的鏈式存儲結構:對于串的鏈式存儲結構,與線性表是相似的,但由于串結構的特殊性(結構中的每個元素數據都是一個字符),如果也簡單地將每個鏈結點存儲一個字符,就會存在很大的空間浪費。因此,一個結點可以考慮存放多個字符,如果最后一個結點未被占滿時,可以使用 "#" 或其他非串值字符補全,如下圖所示:
- 串的鏈式存儲結構除了在鏈接串與串操作時有一定的方便之外,總的來說不如順序存儲靈活,性能也不如順序存儲結構好。
第 6 章:樹
樹(Tree): 樹是
個結點的有限集。當
時稱為空樹。
在任意一棵非空樹中:
? 有且僅有一個特定的結點:根結點(Root);
? 當時,其余結點可分為
個互不相交的有限集
,其中每一個集合本身又是一棵樹,并且稱為根的 子樹(SubTree)。其圖示如下所示:
- 對于 樹 的定義需要強調兩點:
? 結點大于 0 ()時根結點是唯一的,不可能同時存在多個根結點。
? 子結點大于 0 ()時,子樹的個數沒有限制,但它們一定是互不相交的。下圖所示的結構就不符合樹的定義,因為它們都有相交的子樹:
樹 其實也是一種遞歸的實現,即樹的定義之中還用到了樹的概念。
對比線性表與樹的結構,它們有很大的不同:
單獨使用順序存儲結構(即數組)無法很好地實現樹的存儲概念,不過如果充分利用順序存儲和鏈式存儲結構的特點,則完全可以實現對數的存儲結構的表示。
二叉樹(Binary Tree):是
個結點的有限集合,該集合或者為空集(稱為空二叉樹),或者由一個根結點和兩棵互不相交的,分別稱為根結點的左子樹和右子樹的二叉樹組成。如下圖所示:
二叉樹的特點:
? 每個結點最多只能有兩棵子樹。
? 左子樹和右子樹是有順序的,次序不能任意顛倒。
? 即使樹中某結點只有一棵子樹,也要區分它是左子樹還是右子樹。二叉樹具有以下五種基本形態:
? 空二叉樹
? 只有一個跟結點
? 根結點只有左子樹
? 根結點只有右子樹
? 根結點既有左子樹又有右子樹二叉樹的特性:(理解并牢記二叉樹的以下特性,可以幫助我們更好地使用它)
? 在二叉樹的第層上至多有
個結點(
)。比如第一層是根結點,只有一個;第二層有兩個:根結點的左子樹和右子樹···
? 深度為的二叉樹至多有
個結點(
)。比如深度為 1,則至多只有 1 個結點,即根結點;深度為 2,則至多只有 3 個結點:根結點,根結點的左子樹,根結點的右子樹···
? 對任何一棵二叉樹,如果其葉子結點點數為
,度(即子結點數)為 2 的結點數為
,則
。
? 具有個結點的完全二叉樹的深度為
(
表示不大于
的最大整數)。
? 如果對一棵樹有個結點的完全二叉樹(其深度為
)的結點按層序編號(從第 1 層到第
層,每層從左到右),對任一結點
有:
1).如果,則結點
是二叉樹的根,無雙親;如果
,則其雙親是結點
。
2).如果,則結點
無左孩子(結點
為葉子結點);否則其左孩子是結點
.
3).如果,則結點
無右孩子;否則其右孩子是結點
。
前面提及到順序存儲對數這種一對多的關系結構實現起來是比較困難的,但是對于二叉樹,由于它的特殊性,使得用順序存儲結構也可以實現。
二叉樹的遍歷(traversing binary tree):是指從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問一次且僅被訪問一次。
二叉樹的遍歷方式有很多,如果我們限制了從左到右的習慣方式,那么主要就分為四種:
??? 前序遍歷:規則是先訪問根結點,然后前序遍歷左子樹,再前序遍歷右子樹(總結:根結點 -> 左子樹 -> 右子樹)。如下圖所示,遍歷的順序為:ABDGHCEIF。
??? 中序遍歷:從根結點開始(注意并不是先訪問根結點),中序遍歷根結點的左子樹,然后再訪問根結點,最后中序遍歷右子樹(總結:左子樹 -> 根結點 -> 右子樹)。如下圖所示,遍歷的順序為:GDHBAEICF。
??? 后序遍歷:從左到右先葉子后結點的方式遍歷訪問左右子樹,最后訪問根結點(總結:**從左到右訪問葉子結點 -> 根結點)。如下圖所示,遍歷的順序為:GHDBIEFCA。
??? 層序遍歷:從樹的第一層,即根結點開始訪問,從上而下逐層遍歷,在同一層中,按從左到右的順序對結點逐個訪問(總結:第一層 -> 第二層(從左到右訪問結點)-> ··· -> 最后一層(從左到右訪問結點)。如下圖所示,遍歷的順序為:ABCDEFGHI。
樹,森林看似復雜,其實它們都可以轉化為簡單的二叉樹來處理,這樣就使得面對樹和森林的數據結構時,編碼實現成為了可能。
最基本的壓縮編碼方法:赫夫曼編碼
第 7 章:圖
-
圖(Graph):由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:
,其中,
表示一個圖,
是圖
中的頂點的集合,
是圖
中邊的集合。
在線性表中,數據元素之間是被串起來的,僅有線性關系,每個數據元素只有一個直接前驅和一個直接后驅。
在樹形結構中,數據元素之間有著明顯的層次關系,并且每一層上的數據元素可能和下一層中多個元素相關,但只能和上一層中一個元素相關。
圖是一種較線性表和樹更加復雜的數據結構。在圖形結構中,結點之間的關系可以是任意的,圖中任意兩個數據元素之間都可能相關。對于圖的定義,有以下幾個地方需要明確注意:
? 線性表中我們把數據元素叫元素,樹中將數據元素叫結點,在圖中的數據元素,我們稱之為頂點(Vertex)。
? 線性表可以沒有數據元素,稱為空表;樹中可以沒有結點,稱為空樹;而在圖結構中,不允許沒有頂點。在定義中,若是頂點的集合,即強調了頂點集合
有窮非空。
? 線性表中,相鄰的數據元素之間具有線性關系;樹結構中,相鄰兩層的結點具有層次關系;而 圖中,任意兩個頂點之間都可能存在關系,頂點之間的邏輯關系用邊進行表示,邊集可以是空的。圖的種類:
??? 無向圖(Undirected graphs):若頂點 到
之間的邊沒有方向,則稱這條邊為 無向邊(Edge),用無序偶對
來表示。如果圖中任意兩個頂點之間的邊都是無向邊,則稱該圖為 無向圖。下圖所示即為無向圖:
由于無向圖是無方向的,連接頂點 與
的邊,可以表示成無序對
,也可以寫成
。
對于上圖中的無向圖 來說,
,其中頂點集合
;邊集合
??? 有向圖(Directed graphs):若從頂點 到
的邊有方向,則稱這條邊為 有向邊,也稱為 弧(Arc)。用有序偶
來表示,
稱為弧尾(Tail),
稱為弧頭(Head)。如果圖中任意兩個頂點之間的邊都是有向邊,則稱該圖為 有向圖(Directed graphs)。如下圖所示即為一個有向圖:
連接到頂點 到
的有向邊就是弧,
是弧尾,
是弧頭,
表示弧,注意不能寫成
。
對于上圖的有向圖 來說,
,其中頂點集合
;弧集合
。
注:看清楚了,無向邊用小括號 表示,而有向邊則是使用尖括號
表示。
??? 簡單圖:在圖中,若不存在頂點到其自身的邊,且同一條邊不重復出現,則稱這樣的圖為簡單圖。如下所示的兩個圖就不屬于簡單圖:
??? 完全無向圖:在無向圖中,如果任意兩個頂點之間都存在邊,則稱該圖為 無向完全圖。如下圖所示即為一個無向完全圖:
??? 有向完全圖:在有向圖中,如果任意兩個頂點之間都存在 方向互為相反 的兩條弧,則稱該圖為 有向完全圖。如下圖所示即為一個有向完全圖:
無向圖頂點的邊數叫做 度,有向圖頂點分為 入度(箭頭朝自己) 和 出度(箭頭朝外)。
有些圖的邊或弧具有與它相關的數字,這種 與圖的邊或弧相關的數叫做權(Weight)。這些權可以表示從一個頂點到另一個頂點的距離或耗費。這種帶權的圖通常稱為網(Network)。下圖就是一張帶權的圖,即標識中國四大城市的直線距離的網,此圖中的權就是兩地的距離。
圖結構中,路徑的長度是路徑上的邊或弧的數據。
第一個頂點到最后一個頂點相同的路徑稱為 回環 或 環(Cycle)。
序列中頂點不重復出現的路徑稱為 簡單路徑。
除了第一個頂點和最后一個頂點之外,其余頂點不重復出現的回路,稱為 簡單回路 或 簡單環。
下圖所示兩個圖粗線都構成環,左側的環只有第一個頂點和最后一個頂點都是 B,其余頂點沒有重復出現,因此其是一個簡單環。而右側的環,由于頂點 C 的重復,因此它就不是簡單環了。
圖中頂點間存在 路徑,兩頂點存在路徑則說明是 連通 的,如果路徑最終回到起始點則成為 環,當中不重復叫 簡單路徑。若任意兩頂點都是連通的,則圖就是 連通圖,有向則稱為 強連通圖。圖中有子圖,若子圖極大連通則就是 連通分量,有向的則稱為 強連通分量。
無向圖中連通且
個頂點
條邊叫 生成樹。有向圖中一頂點入度為
其余頂點入度為
的叫 有向樹。一個有向圖由若干棵有向樹構成生成 森林。
由于圖的結構比較復雜,任意兩個頂點之間都可能存在聯系,因此無法以數據元素在內存中的物理位置來表示元素之間的關系,也就是說,圖不可能用簡單的順序存儲結構(即數組)來表示。而多重鏈表盡管可以實現圖結構(即以一個數據域和多個指針域組成的結點表示圖中的一個頂點),但是卻存在內存浪費或操作不便的問題。因此,圖存儲結構最終還是得通過結合順序存儲和鏈式存儲才能做到比較好地實現。
圖的遍歷和樹的遍歷類似,我們希望 從圖中某一頂點觸發,遍歷圖中其余頂點,且使每一個頂點僅被訪問一次,這一過程就叫做圖的遍歷(Traversing Graph)。
圖的遍歷通常有兩種方案:深度優先遍歷 和 廣度優先遍歷。
? 深度優先遍歷(Depth First Search):也稱為 深度優先搜索,簡稱 DFS。對于連通圖,它從圖中某個頂點觸發,訪問此頂點,然后從
的未被訪問的鄰接點出發深度優先遍歷圖,直至圖中所有和
有路徑相通的頂點都被訪問到。而對于非連通圖,只需要對它的連通分量分別進行深度優先遍歷,即在先前一個頂點進行一次深度優先遍歷后,若圖中尚未有頂點未被訪問,則另選圖中一個未曾被訪問的頂點作為起始點,重復上述過程,直至圖中所有頂點都被訪問到為止。(所謂深度優先搜索,就是選擇一個頂點開始走,期間對于走過的頂點就不在訪問,走其他未被訪問的,一直走到無路可走。若此時還有頂點未走過,選擇一個,重復上述過程。)。
? 廣度優先遍歷(Breadth First Search):又稱為 廣度優先搜索,簡稱 BFS。深度優先遍歷與廣度優先遍歷算法在時間復雜度上是一樣的,不同之處僅僅在于對頂點訪問的順序不同。
深度優先更適合目標比較明確,以找到目標為主要目的的情況;而廣度優先更適合在不斷擴大遍歷范圍時找到相對最優解的情況。
把構造連通網的最小代價生成樹稱為 最小生成樹(Minimum Cost Spanning Tree)。
找連通網的最小生成樹,經典的算法有兩種:普里姆(Prim)算法 和 克魯斯卡爾(Kruskal)算法。
在一個表示工程的有向圖中,用頂點表示活動,用弧表示活動之間的優先關系,這樣的有向圖為頂點表示活動的網,我們成為 AOV 網(Activity On Vertext Network)。AOV 網中的弧表示活動之間存在的某種制約關系,同時 AOV 網中不能存在回路。
拓撲序列:設
是一個具有
個頂點的有向圖,
中的頂點序列
,滿足若從頂點
到
有一條路徑,則在頂點序列中頂點
必在頂點
之前,則我們將這樣的頂點序列稱為一個 拓撲序列。
所謂 拓撲排序,其實就是對一個有向圖構造拓撲序列的過程。
對 AOV 網進行拓撲排序的基本思路是:從 AOV 網中選擇一個入度為 0 的頂點輸出,然后刪去此頂點,并刪除以此頂點為尾的弧,繼續重復此步驟,直到輸出全部頂點或者 AOV 網中不存在入度為 0 的頂點為止。
在一個表示工程的帶權有向圖中,用頂點表示事件,用有向邊表示活動,用邊上的權值表示活動的持續時間,這種有向圖的邊表示活動的網,我們稱之為 AOE 網(Activity On Edge Network)。
我們把 AOE 網中沒有入邊的頂點稱為始點或源點,沒有出邊的頂點稱為終點或匯點。由于一個工程,總有一個開始,一個結束,所以正常情況下,AOE 網只有一個源點一個匯點。
我們把路徑上各個活動所持續的時間之和稱為 路徑長度,從源點到匯點具有最大長度的路徑叫 關鍵路徑,在關鍵路徑上的活動叫關鍵活動。