第8章 圖
目錄- 一、圖的基本概念
- 二、圖的基本運算
- 三、圖的基本存盤結構
- 3.1 鄰接矩陣及其實作
- 3.1.1 鄰接矩陣存盤結構
- 3.1.2 建立網路的鄰接矩陣演算法(演算法)
- 3.2 鄰接表及其實作
- 3.2.1 鄰接表存盤結構
- 3.2.2 建立無向圖的鄰接表演算法(演算法)
- 3.3 鄰接多重表
- 3.1 鄰接矩陣及其實作
- 四、圖的遍歷
- 4.1 深度優先遍歷(DFS)
- 4.2 廣度優先遍歷 (BFS)
- 五、生成樹與最小生成樹 (無向網)
- 5.1 最小生成樹的定義
- 5.2 最小生成樹的普里姆(Prim)演算法
- 5.3 最小生成樹的克魯斯卡爾(Kruskal)演算法
- 六、最短路徑 (有向網)
- 6.1 單源最短路徑(Dijkstra演算法)
- 6.2 所有頂點對的最短路徑 (Floyd演算法)
- 七、拓撲排序 (AOV網)
- 八、關鍵路徑 (AOE網)
- 九、演算法設計題
- 9.1 求一個頂點的度(真題)(演算法)
- 9.2 往圖中插入一個頂點(演算法)
- 9.3 往圖中插入一條邊(演算法)
- 十、錯題集
一、圖的基本概念
- 圖 \(G\) 的頂點集:記作 \(V(G)\)
- 圖 \(G\) 的邊集:記作 \(E(G)\)
- 無向圖的邊:頂點 \(v\) 到 頂點 \(u\) 的邊記作 \((u,v)\)
- 有向圖的邊:頂點 \(v\) 到 頂點 \(u\) 的邊記作 \(<u,v>\)
- 鄰接點:若 \((v,u)\) 是一條無向邊,則稱 \(u\) 和 \(v\) 互為鄰接點
- 頂點和邊數的關系:
- \(n\) 個頂點的無向圖,其邊數 \(e\) 小于等于 \(n(n-1)/2\),邊數恰好為 \(n(n-1)/2\) 的無向圖稱為無向完全圖
- \(n\) 個頂點的有向圖,其邊數 \(e\) 小于等于 \(n(n-1)\),邊數恰好為 \(n(n-1\) 的有向圖稱為有向完全圖
- 無向圖頂點的度:頂點相關聯的邊的數目,頂點 \(v\) 的度記為 \(D(v)\)
- 有向圖頂點的度:
- 入度:以頂點 \(v\) 作為終點的邊的數目,記為 \(ID(v)\)
- 出度:以頂點 \(v\) 作為始點的邊的數目,記為 \(OD(v)\)
- 注:有向圖頂點的度等于頂點的入度和出度之和
- 頂點數 \(n\)、邊數 \(e\) 和度數的關系:\(e=\frac{1}{2}\sum_{i=1}^n{D(v_i)}\)
- 有向圖路徑:存在一個頂點序列 \(v,v_1,v_2,\cdots,v_m,u\),且 \((v,v_1),(v_1,v_2),\cdots,(v_m,u)\) 都屬于 \(E(G)\),則該頂點序列為 \(v\) 到 \(u\) 的一條路徑
- 無向圖路徑:有向圖路徑:存在一個頂點序列 \(v,v_1,v_2,\cdots,v_m,u\),且 \(<v,v_1>,<v_1,v_2>,\cdots,<v_m,u>\) 都屬于 \(E(G)\),則該頂點序列為 \(v\) 到 \(u\) 的一條路徑
- 簡單路徑:一條路徑除了起點 \(v\) 和終點 \(u\) 之外,其余頂點均不相同,該路徑稱之為一條簡單路徑
- 簡單回路(簡單環):起點和終點相同(\(v=u\))的簡單路徑
- 無向圖連通的概念:
- 無向圖的連通:頂點 \(v\) 到 \(u\) 之間有路徑,則稱 \(v\) 和 \(u\) 是連通的
- 連通圖(無向圖):\(V(G)\) 中任意兩個不同的頂點 \(v\) 和 \(u\) 都連通(即有路徑),則 \(G\) 為連通圖
- 連通分量:無向圖 \(G\) 的極大連通子圖(任何連通圖的連通分量都是其本身,非連通的無向圖有多個連通分量)
- 有向圖連通的概念:
- 強連通圖:\(V(G)\) 中任意兩個不同的頂點 \(v\) 和 \(u\),都存在從 \(v\) 到 \(u\) 和從 \(u\) 到 \(v\) 的路徑
- 強連通分量:有向圖的極大強連通子圖(任何強連通圖的唯一強連通分量是其自身,非強連通的有向圖有多個強連通分量)
- 連通分量和強連通分量注意事項:單個頂點可以屬于一個單獨的連通分量或強連通分量
- 權:圖的每條邊上附上相關的數值
- 網路(帶權圖):圖的每條邊都賦上一個權
二、圖的基本運算
- 略
三、圖的基本存盤結構
- 注:對于以下圖的存盤結構,都假定頂點序號從 \(0\) 開始,圖 \(G\) 的頂點集一般形式為 \(V(G)=\{v_0,\cdots,v_i,\cdots,v_{n-1}\}\)
3.1 鄰接矩陣及其實作
-
圖的鄰接矩陣表示法:用兩個表格分別存盤資料元素(頂點)的資訊和資料之間的關聯(邊)資訊
- 一維陣列(順序表):存盤資料元素的資訊
- 二維陣列(鄰接矩陣):存盤資料元素之間的關系
-
鄰接矩陣的性質:若 \((v_i,v_j)或<v_i,v_j>\in{E(G)}\),\(A[i,j]=1\);若 \((v_i,v_j)or<v_i,v_j>\notin{E(G)}\),\(A[i,j]=0\)
-
無向圖的鄰接矩陣:該鄰接矩陣一定是對稱的,可以采用上(下)三角矩陣進行壓縮存盤,存盤空間為 \(n(n+1)/2\)
-
有向圖的鄰接矩陣:該鄰接矩陣不一定是對稱的,存盤空間為 \(n^2\)
-
無向圖頂點 \(v_i\) 的度數:\(D(v_i) = \sum_{j=0}^{n-1}A[i,j]=\sum_{j=0}^{n-1}A[j,i]\) (對稱矩陣\(A=A^T\))
-
有向圖頂點 \(v_i\) 的度數:
- 出度:\(OD(v_i)=\sum_{j=0}^{n-1}A[i,j]\)
- 入度:\(ID(v_i) = \sum_{j=0}^{n-1}A[j,i]\)
-
圖鄰接矩陣圖:

-
網路的鄰接矩陣性質:
- 圖網路鄰接矩陣圖:

3.1.1 鄰接矩陣存盤結構
#define FINITY 5000 // 用5000表示無窮大
#define M 20 // 最大頂點數
typedef char vertextype; // 頂點值型別
typedef int edgetype; // 權值型別
typedef struct {
vertextype vexs[M]; // 頂點資訊域
edgetype edges[M][M]; // 鄰接矩陣
int n, e; // 圖中頂點總數和邊數
} Mgraph;
3.1.2 建立網路的鄰接矩陣演算法(演算法)
- 演算法步驟:
- 打開檔案
- 讀入圖中的頂點數和邊數
- 讀入圖中的頂點值
- 初始化鄰接矩陣
- 讀入網路中的邊
- 建立無向圖鄰接矩陣
- 關閉檔案
3.2 鄰接表及其實作
- 鄰接表與鄰接矩陣的區別:
- 頂點個數為 \(n\) 的圖,鄰接矩陣的存盤空間為 \(n^2\) ,而使用鄰接表則可節省很多空間
- 鄰接矩陣表示法唯一,而鄰接表的表示法不唯一
- 鄰接表中的兩個結點:
- 表頭結點:存盤頂點的資料域(\(vertex\))和頭指標域(\(firstedge\))
- 邊表結點:鄰接點域(\(adjvex\))和鏈域(\(next\))
- 鄰接表的隨機訪問任意頂點的構造:讓所有頭結點順序存盤在一個向量中
- 圖無向圖鄰接表:

- 有向圖
- 出邊表(鄰接表):表結點存盤以頂點 \(v\) 為始點射出的一條邊
- 入邊表(逆鄰接表):表結點存盤以頂點 \(v\) 為終點射出的一條邊
- 圖有向圖的鄰接表:

- 鄰接表存盤空間:無向圖(\(n\) 個頂點和 \(e\) 條邊)用鄰接表存盤需要 \(n\) 個頭結點和 \(2e\) 個邊結點
- 注:當 \(e\) 遠小于 \(n(n-1)/2\) 時,鄰接表存盤圖比鄰接矩陣存盤圖節省空間
- 無向圖的度(鄰接表):頂點 \(v_i\) 的度為第 \(i\) 個鏈表中結點的個數
- 有向圖的度(鄰接表-出邊表):
- 出度:第 \(i\) 個鏈表中的結點的個數
- 入度:所有鏈表中其鄰接點域的值為 \(i\) 的結點的個數
3.2.1 鄰接表存盤結構
#define M 20 // 預定義圖的最大頂點數
typedef char datatype; // 定點資訊資料域
// 邊表結點型別
typedef struct node {
int adjvex; // 鄰接點
struct node *next;
} edgenode;
// 頭結點型別
typedef struct vnode {
datatype vertex; // 頂點資訊
edgenode *firstedge; // 鄰接鏈表頭指標
} vertexnode;
// 鄰接表型別
typedef struct {
vertexnode adjlist[M]; // 存放頭結點的順序表
int n, e; // 圖的頂點數與邊數
} linkedgraph;
3.2.2 建立無向圖的鄰接表演算法(演算法)
- 演算法步驟:
- 打開檔案
- 讀入頂點數和邊數
- 讀入頂點資訊
- 邊表置為空
- 回圈 e(邊數) 次建立邊表
- 輸入無序對 \((i,j)\)
- 鄰接點序號為 \(j\)
- 將新結點 \(*s\) 插入頂點 \(v_i\) 的邊表頭部
- 關閉檔案
3.3 鄰接多重表
-
鄰接多重表由兩個表組成:
-
表頭結點:\(vertex\) 域存盤頂點的相關資訊;\(firstedge\) 存盤第一條關聯于 \(vertex\) 頂點的邊
-
邊表結點:\(mark\) 域標志圖的遍歷演算法中是否被搜索過;\(vex_i\) 和 \(vex_j\) 表示邊的兩個頂點在圖中的位序;\(link_i\) 和 \(link_j\) 表示兩個邊表結點指標,
- \(link_i\) 指向關聯于頂點 \(vex_i\)的下一條邊;\(link_j\) 指向關聯于頂點 \(vex_j\) 的下一條邊
-
邊表結點的順序如下表所示:
-
\(mark\) \(vex_i\) \(link_i\) \(vex_j\) \(link_j\)
-
-
-
四、圖的遍歷
4.1 深度優先遍歷(DFS)
-
深度優先遍歷步驟:
- 選取一個源點 \(v\in{V}\),把 \(v\) 標記為已被訪問
- 使用深度優先搜索方法依次搜索 \(v\) 的所有鄰接點 \(w\)
- 如果 \(w\) 未被訪問,以 \(w\) 為新的出發點繼續進行深度優先遍歷
- 如果從 \(v\) 出發,有路的頂點都被訪問過,則從 \(v\) 的搜索程序結束
- 如果圖中還有未被訪問過的頂點(該圖有多個連通分量或者強連通分量),則再任選一個未被訪問的頂點開始新的搜索
-
注:深度優先遍歷的順序是不一定的,但是,當采用鄰接表存盤結構并且存盤結構已確定的情況下,遍歷的結果將是確定的
-
圖深度優先遍歷:

4.2 廣度優先遍歷 (BFS)
- 廣度優先遍歷步驟:
- 從圖中某個源點 \(v\) 出發
- 訪問頂點 \(v\) 后,盡可能先橫向搜索 \(v\) 的所有鄰接點
- 在訪問 \(v\) 的各個鄰接點 \(w_k,\cdots,w_k\) 后,從這些鄰接點出發依次訪問與 \(w_1,\cdots,w_k\) 鄰接的所有未曾訪問過的頂點
- 如果 \(G\) 是連通圖,訪問完成;否則另選一個尚未訪問的頂點作為新源點繼續上述搜索程序,知道圖 \(G\) 的所有頂點均被訪問
- 注:廣度優先遍歷無向圖的程序中,呼叫 \(bfs(函式:從頂點\,i\,出發廣度優先遍歷圖\,g\,的連通分量)\) 的次數就是該圖連通分量的個數
- 圖廣度優先遍歷:

五、生成樹與最小生成樹 (無向網)
- 生成樹:對于一個無向的連通圖 \(G\),設 \(G'\) 是它的一個子圖,如果 \(G'\) 中包含了 \(G\) 中所有的頂點,且 \(G'\) 是無回路的連通圖,則稱 \(G'\) 為 \(G\) 的一顆生成樹
- 圖生成樹:

- 生成森林:如果 \(G\) 是非連通的無向圖,需要若干次從外部呼叫 \(DFS(或BFS)\) 演算法才能完成對 \(G\) 的遍歷,每一次外部呼叫,只能訪問 \(G\) 的一個連通分量,經過該連通分量的頂點和邊構造出一顆生成樹,則 \(G\) 的各個連通分量的生成樹組成了 \(G\) 的生成森林
- 圖生成森林:

5.1 最小生成樹的定義
- 生成樹的權:生成樹 \(T\) 的各邊的權值總和,記作 \(W(T)=\sum_{(u,v)\in{E}}w_{uv}\),其中 \(E\) 表示 \(T\) 的邊集,\(w_{uv}\) 表示邊 \((u,v)\) 的權
- 最小生成樹的權:總權值 \(W(T)\) 最小的生成樹稱為最小生成樹
- 構造最小生成樹的準則:
- 必須只使用該網路中的邊來構造最小生成樹
- 必須使用且僅使用 \(n-1\) 條邊來連接網路中的 \(n\) 個頂點
- 不能使用產生回路的邊
5.2 最小生成樹的普里姆(Prim)演算法
- 兩棲邊:假設 \(G=(V,E)\) 是一個連通圖,\(U\) 是頂點集 \(V\) 的一個非空真子集,若 \((u,v)\) 是滿足 \(u\in{U},v\in{V-U}\) 的邊(稱這個邊為兩棲邊),且 \((u,v)\) 在所有的兩棲邊中具有最小的權值(此時,稱 \((u,v)\) 為最小兩棲邊)
- Prim演算法步驟:
- 初始化 \(U=\{u_0\},TREE=\{\}\)
- 如果 \(U=V(G)\),則輸出最小生成樹 \(T\),并結束演算法
- 在所有兩棲邊中找一條權最小的邊 \((u,v)\)(若候選兩棲邊中的最小邊不止一條,可任選其中的一條),將邊 \((u,v)\) 加入到邊集 \(TREE\) 中,并將頂點 \(v\) 并入集合 \(U\) 中
- 由于新頂點的加入,\(U\) 的狀態發生變化,需要對 \(U\) 與 \(V-U\) 之間的兩棲邊進行調整
- 轉步驟 \(2\)
- 下圖步驟順序(\(V=\{A,B,C,D,E,F\}\)):
- \(U = \{A\}\),\(V-U=\{B,C,D,E,F\}\),\(TREE=\{\}\),兩棲邊 \((A,B),(A,C),(A,D),(A,E),(A,F)\),最小兩棲邊 \((A,B)\)
- \(U = \{A,B\}\),\(V-U=\{C,D,E,F\}\),\(TREE=\{(A,B)\}\),兩棲邊 \((B,C),(B,D),(B,F),(A,E)\)(\((B,C)\) 比 (\(A,C)\) 小,因此做一個替換),最小兩棲邊 \((B,D)\)
- \(U = \{A,B,D\}\),\(V-U=\{C,E,F\}\),\(TREE=\{(A,B),(B,D)\}\),兩棲邊 \((B,C),(B,F),(A,E)\),最小兩棲邊 \((B,F)\)
- \(U = \{A,B,D,F\}\),\(V-U=\{C,E\}\),\(TREE=\{(A,B),(B,D),(B,F)\}\),兩棲邊 \((B,C),((F,E)\),最小兩棲邊 \((B,C)\)
- \(U = \{A,B,D,F,C\}\),\(V-U=\{E\}\),\(TREE=\{(A,B),(B,D),(B,F),(B,C)\}\),兩棲邊 \((F,E)\),最小兩棲邊 \((F,E)\)
- \(U = \{A,B,D,F,C,E\}\),\(V-U=\{\}\),\(TREE=\{(A,B),(B,D),(B,F),(B,C),(F,E)\}\),兩棲邊 \(\{\}\),最小兩棲邊 \(\{\}\)
- 圖prim演算法:

5.3 最小生成樹的克魯斯卡爾(Kruskal)演算法
- 演算法步驟:
- 將圖 \(G\) 看做一個森林,每個頂點為一棵獨立的樹
- 將所有的邊加入集合 \(S\),即一開始 \(S = E\)
- 從 \(S\) 中拿出一條最短的邊 \((u,v)\),如果 \((u,v)\) 不在同一棵樹內,則連接 \(u,v\) 合并這兩棵樹,同時將 \((u,v)\) 加入生成樹的邊集 \(E'\)
- 重復步驟 \(3\) 直到所有點屬于同一棵樹,邊集 \(E'\) 就是一棵最小生成樹
- 圖kruskal演算法:

六、最短路徑 (有向網)
6.1 單源最短路徑(Dijkstra演算法)
-
距離向量 \(d\):表示源點可以途徑 \(S\) 中的頂點到達 \(V-S\) 中頂點的距離
-
路徑向量 \(p\):保存路徑上第 \(i\) 個頂點的前驅頂點序號(沒有的話,默認為 \(-1\))
-
演算法步驟:
-
找到與源點 \(v\) 最近的頂點,并將該頂點并入最終集合 \(S\)
-
根據找到的最近的頂點更新從源點 \(v\) 出發到集合 \(V-S\) 上可達頂點的最短路徑
-
重復以上操作
-
-
圖Dijkstra演算法:

-
上圖求單源最短路徑步驟:
- 初始化:從源點 \(v1\) 出發得到矩陣,到達個點的最小路徑是 \(D_{12}=10\),\(D_{0}=\left[\begin{array}{cccc} v1 &v2 &v3 &v4 &v5\\ 0 &10 &\infty &30 &100\\ \end{array}\right ]\)
- 第一次:從 \(v2\) 點出發,\(v1\) 和 \(v2\) 保持不變,迭代剩下點 \((v3,v4,v5)\) 的距離后,剩余點的最短路徑是 \(v4\),\(D_{1}=\left[\begin{array}{cccc} v1 &v2 &v3 &v4 &v5\\ 0 &10 &60(10+50) &30 &100\\ \end{array}\right ]\)
- 第二次:從 \(v4\) 點 出發,\(v1,v2,v4\) 保持不變,優化剩余點 \((v3,v5)\) 的最短距離,剩余點的最短路徑是 \(v3\),\(D_{2}=\left[\begin{array}{cccc} v1 &v2 &v3 &v4 &v5\\ 0 &10 &50(20+30) &30 &90(30+60)\\ \end{array}\right ]\)
- 第三次:從 \(v3\) 點出發,\(v1,v2,v4,v3\) 保持不變,優化剩余點 $v5 \(的最短路徑,\)D_{3}=\left[\begin{array}{cccc} v1 &v2 &v3 &v4 &v5\ 0 &10 &50 &30 &60(20+30+10)\ \end{array}\right ]$
-
源點 \(v1\) 的 \(Dijkstra演算法\) 的最短路徑(如下表):
-
中間頂點 終點 路徑長度 2 10 4 3 50 4 30 4;3 5 60
-
-
距離向量 \(d\) 和路徑向量 \(p\):
-
圖Dijkstra演算法的輔助陣列:

6.2 所有頂點對的最短路徑 (Floyd演算法)
- 演算法步驟:略
七、拓撲排序 (AOV網)
- \(AOV網\) 邊的作用:表示活動間(一個頂點表示一個活動)的優先關系
- 注:(真題)拓撲排序可以判斷圖中有沒有回路(深度遍歷優先演算法也可以)
- 演算法步驟
- 在有向圖中找一個沒有前驅(入度為 \(0\))的頂點并輸出
- 在圖中洗掉該頂點以及所有從該頂點發出的有向邊
- 反復執行步驟 \(1\) 和 \(2\),知道所有頂點均被輸出,或者 \(AOV\) 網中再也沒有入度為 \(0\) 的頂點存在為止
- 圖拓撲排序:

八、關鍵路徑 (AOE網)
- \(AOE網\) 邊的作用:表示活動(一個頂點表示一個活動)持續的時間
- 事件可能的最早開始時間 \(v_e(i)\):對于某一事件 \(v_i\),它可能的最早發生時間 \(v_e(i)\) 是從源點到頂點 \(v_i\) 的最大路徑長度
- \[\begin{cases}v_e(0) = 0\\v_e(i) = max\{v_e(j)+活動<v_j,v_i>持續的時間\}(1\leq{i}\leq{n-1)}\end{cases} \]
- 事件允許的最晚發生時間 \(v_l(i)\):對于某一事件 \(v_i\),它允許的最晚發生時間是在保證按時完成整個工程的前提下,該事件最晚必須發生的時間
- \[\begin{cases}v_l(n-1)=v_e(n-1)\\v_l=min\{v_l(j)-len(<v_i,v_j>)\}(0\leq{i}\leq{n-2})\end{cases} \]
- 最早可能開始時間 \(e(i)\) 和 允許的最晚發生時間 \(l(i)\):
- \[\begin{cases}e(k)=v_e(i)\\l(k)=v_l(j)-len(<v_i,v_j>)\end{cases} \]
- 注:\(k\) 為某條邊,即 \(<v_i,v_j>\) 為 \(a_k\)
- 關鍵活動:\(e(i) = l(i)\) 的頂點為關鍵活動
- 演算法步驟:
- 求出各個事件的 \(v_e\) 和 \(v_l\) 值后
- 計算原則:\(v_e\) 的值越大越好;\(v_l\) 的值越小越好
- 求出活動的最早可能開始時間 \(e(i)\) 和 允許的最晚發生時間 \(l(i)\)
- 其中滿足 \(e(i)=l(i)\) 的活動就是 \(AOE網\) 中的關鍵活動
- 求出各個事件的 \(v_e\) 和 \(v_l\) 值后
- 圖關鍵路徑圖:

拓撲序列(v0、v1、v2、v4、v3、v6、v7、v5、v8、v9)每個事件的最早開始時間:
ve(0)=0
ve(1)= 8,ve(2)=6,ve(4)=7;
ve(3)=max{ve(1)+len(a3),ve(2)+len(a4)}=16;
ve(6)=max{ve(2)+len(a5),ve(4)+len(a6)}=16;
ve(7)=max{ve(6)+len(a11),ve(4)+len(a7)}=20;
ve(5)= ve(3)+len(a8)=20;
ve(8)=max{ve(3)+len(a9),ve(6)+len(a10),ve(7)+len(a12)}=35;
ve(9)=max{ve(5)+len(a13),ve(8)+len(a14)}=45;
每個事件允許的最晚發生時間如下:
vl(9)=ve(9)=45
vl(8)=vl(9)-len(<v8,v9>)=45-10=35
vl(5)=vl(9)-len(<v5,v9>)=45-14=31
vl(7)=vl(8)-len(<v7,v8>)=35-6=29
vl(6)=min{vl(7)-len(<v6,v7>),vl(8)-len(<v6,v8>)}=min{27,27}=27
vl(3)=min{vl(5)-len(<v3,v5>),vl(8)-len(<v3,v8>)}=min{27,16}=16
vl(4)=min{vl(6)-len(<v4,v6>),vl(7)-len(<v4,v7>)}=min{18,16}=16
vl(2)=min{vl(3)-len(<v2,v3>),vl(6)-len(<v2,v6>)}=min{6,18}=6
vl(1)=vl(3)-len(<v1,v3>)=13
vl(0)=min{vl(1)-8,vl(2)-6,vl(4)-7}=min{5,0,9}=0
-
圖關鍵路徑

-
圖AOE網計算:

九、演算法設計題
9.1 求一個頂點的度(真題)(演算法)
無向圖采用鄰接表作為存盤結構,求一個頂點的度
- 演算法步驟:
- 獲取頂點的第一個結點 \(firstedge\)
- 如果第一個結點存在,回圈獲取后續結點
#define M 20 // 預定義圖的最大頂點數
typedef char datatype; // 頂點資訊資料域
// 邊表結點型別
typedef struct node {
int adjvex; // 鄰接點
struct node *next;
} edgenode;
// 頭結點型別
typedef struct vnode {
datatype vertex; // 頂點資訊
edgenode *firstedge; // 鄰接鏈表頭指標
} vertexnode;
// 鄰接表型別
typedef struct {
vertexnode adjlist[M]; // 存放頭結點的順序表
int n, e; // 圖的頂點數與邊數
} adjgraph;
int d(adjgraph g, int i) {
int k = 0;
edgenode *p;
p = g.adjlist[i].firstedge;
while (p) {
k++;
p = p->next;
}
return k;
}
9.2 往圖中插入一個頂點(演算法)
無向圖采用鄰接表作為存盤結構,往圖中插入一個頂點
- 演算法步驟:
- 查找并判斷待插入的結點是否存在
- 判斷鄰接表是否已滿
- 上述判斷都通過,則插入新頂點
#define M 20 // 預定義圖的最大頂點數
typedef char datatype; // 頂點資訊資料域
// 邊表結點型別
typedef struct node {
int adjvex; // 鄰接點
struct node *next;
} edgenode;
// 頭結點型別
typedef struct vnode {
datatype vertex; // 頂點資訊
edgenode *firstedge; // 鄰接鏈表頭指標
} vertexnode;
// 鄰接表型別
typedef struct {
vertexnode adjlist[M]; // 存放頭結點的順序表
int n, e; // 圖的頂點數與邊數
} adjgraph;
void insertvex(adjgraph *g, datatype x) {
int i = 0, flag = 0;
// 查找待插入的結點是否存在
while (!flag && i < g->n) {
if (g->adjlist[i].vertex == x) flag = 1;
i++;
}
// 判斷結點是否存在
if (flag) {
printf("結點已存在!");
getch();
exit(0);
}
// 判斷鄰接表是否已滿
if (g->n > M) {
printf("鄰接表已滿!");
exit(0);
}
// 插入結點
g->adjlist[g->n].vertex = x;
g->adjlist[g->n].firstedge = NULL;
g->n++;
}
9.3 往圖中插入一條邊(演算法)
無向圖采用鄰接表作為存盤結構,往圖中插入一條邊(本題采用前插法)
- 演算法步驟:
- 判斷邊是否已經存在
- 在頂點 \(i\) 的鏈表中插入鄰接點 \(j\);在頂點 \(j\) 的鏈表中插入鄰接點 \(i\)(使用前插法)
#define M 20 // 預定義圖的最大頂點數
typedef char datatype; // 頂點資訊資料域
// 邊表結點型別
typedef struct node {
int adjvex; // 鄰接點
struct node *next;
} edgenode;
// 頭結點型別
typedef struct vnode {
datatype vertex; // 頂點資訊
edgenode *firstedge; // 鄰接鏈表頭指標
} vertexnode;
// 鄰接表型別
typedef struct {
vertexnode adjlist[M]; // 存放頭結點的順序表
int n, e; // 圖的頂點數與邊數
} adjgraph;
void insertedge(adjgraph *g, int i, int j) {
edgenode *p, *s;
int flag = 0;
// 判斷邊是否存在
if (i < g->n && j < g->n) {
p = g->adjlist[i].firstedge;
while (!flag && p) {
if (p->adjvex == j) flag = 1;
p = p->next;
}
if (flag) {
printf("邊已存在!");
getch();
exit(0);
}
// 插入無向邊(i,j)
s = (edgenode *) malloc(sizeof(edgenode));
s->adjvex = j; // 鄰接點序號為 j
s->next = g->adjlist[i].firstedge;
g->adjlist[i].firstedge = s; // 將新結點*s 插入頂點 vi 的邊表頭部
s = (edgenode *) malloc(sizeof(edgenode));
s->adjvex = i; // 鄰接點序號為 i
s->next = g->adjlist[j].firstedge;
g->adjlist[j].firstedge = s; // 將新結點*s 插入頂點 vj 的邊表頭部
} else
printf("插入邊不合法!");
}
十、錯題集
- (真題)判斷一個有向圖是否存在回路可以利用拓撲排序法和深度優先遍歷演算法
- 在一個帶權連通圖 \(G\) 中,權值最小的邊一定包含在 \(G\) 的最小生成樹中
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/155596.html
標籤:其他
上一篇:第7章 二叉樹
下一篇:第9章 檢索
