第8章 圖
1、圖的基本概念
- 圖 \(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\) 的路徑
- 強連通份量:有向圖的極大強連通子圖(任何強連通圖的惟一強連通份量是其自身,非強連通的有向圖有多個強連通份量)
- 連通份量和強連通份量注意事項:單個頂點能夠屬於一個單獨的連通份量或強連通份量
- 權:圖的每條邊上附上相關的數值
- 網絡(帶權圖):圖的每條邊都賦上一個權
2、圖的基本運算
- 略
3、圖的基本存儲結構
- 注:對於如下圖的存儲結構,都假定頂點序號從 \(0\) 開始,圖 \(G\) 的頂點集通常形式爲 \(V(G)=\{v_0,\cdots,v_i,\cdots,v_{n-1}\}\)
3.1 鄰接矩陣及其實現
-
圖的鄰接矩陣表示法:用兩個表格分別存儲數據元素(頂點)的信息和數據之間的關聯(邊)信息node
- 一維數組(順序表):存儲數據元素的信息
- 二維數組(鄰接矩陣):存儲數據元素之間的關係
-
鄰接矩陣的性質:若 \((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]\)
-
圖鄰接矩陣圖:spa
-
網絡的鄰接矩陣性質:設計
\[A[i,j]= \begin{cases} W_{ij} &\qquad \text{當$(v_i,v_j)$或$<v_i,v_j>$$\in$E(G)} \\ 0 &\qquad \text{當$(v_i,v_j)$或$<v_i,v_j>$$\notin$E(G)且i=j} \\ \infin &\qquad \text{當$(v_i,v_j)$或$<v_i,v_j>$$\notin$E(G)且i$\neq$j} \end{cases} \]
- 圖網絡鄰接矩陣圖:
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 鄰接多重表
-
鄰接多重表由兩個表組成:3d
-
表頭結點:\(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、圖的遍歷
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\,的連通份量)\) 的次數就是該圖連通份量的個數
- 圖廣度優先遍歷:
5、生成樹與最小生成樹 (無向網)
- 生成樹:對於一個無向的連通圖 \(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、最短路徑 (有向網)
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算法)
- 算法步驟:略
7、拓撲排序 (AOV網)
- \(AOV網\) 邊的做用:表示活動間(一個頂點表示一個活動)的優先關係
- 注:(真題)拓撲排序能夠判斷圖中有沒有迴路(深度遍歷優先算法也能夠)
- 算法步驟
- 在有向圖中找一個沒有前驅(入度爲 \(0\))的頂點並輸出
- 在圖中刪除該頂點以及全部從該頂點發出的有向邊
- 反覆執行步驟 \(1\) 和 \(2\),知道全部頂點均被輸出,或者 \(AOV\) 網中再也沒有入度爲 \(0\) 的頂點存在爲止
- 圖拓撲排序:
8、關鍵路徑 (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網\) 中的關鍵活動
- 圖關鍵路徑圖:
拓撲序列(v0、v一、v二、v四、v三、v六、v七、v五、v八、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、算法設計題
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("插入邊不合法!");
}
10、錯題集
- (真題)判斷一個有向圖是否存在迴路能夠利用拓撲排序法和深度優先遍歷算法
- 在一個帶權連通圖 \(G\) 中,權值最小的邊必定包含在 \(G\) 的最小生成樹中