概念
圖的定義
圖(Graph)是由頂點的有窮非空集合和頂點之間的邊的集合組成,通常
表示爲: G(V,E)。其中,G 表示一個圖,V 是圖 G 中頂點的集合,E 是
圖 G 中邊的集合。
需要注意:
-
圖中數據元素叫做頂點(Vertext)。
-
在圖中,不允許沒有頂點。若 V 是圖的頂點的集合,那麼,V 是非空
有窮集合。 -
圖的任意兩個頂點之間都可能有關係,它們的關係用邊來表示。邊集可
以是空的。
其他概念
無向邊
若頂點 $V_i$ 到 $V_j$ 之間的邊沒有方向,這條邊就叫做無向邊(Edge),
用無序偶對 ($V_iV_j$) 來表示。
無向圖
如果圖中任意兩個頂點之間的邊都是無向邊,則稱該圖爲無項圖(Undirected graphs)。
有向邊
若從頂點 $V_i$ 到 $V_j$ 的邊有方向,則稱這條邊爲有向邊,也稱爲弧 (Arc)。
這條有向邊用有序偶 $<V_i,V_j>來表示,$V_j是弧尾(Tail),$V_j$是弧頭(Head)。
有向圖
如果圖中任意兩個頂點之間的邊都是有向邊,這個圖就是有向圖。
無向邊用小括號 “()”表示,有向邊用尖括號“<>”表示。
簡單圖
在圖中,若不存在頂點到其自身的邊,且同一條邊不重複出現,這樣的圖就是簡單圖。
數據結構_圖_簡單圖
無向完全圖
在無向圖中,如何任意兩點之間都存在邊,這個圖就是無向完全圖。
n
個頂點的無向完全圖有 ${n(n-1)} \over 2$ 條邊。
有向完全圖
在有向圖中,如果任意兩點之間都存在方向互爲相反的兩條弧,這個圖就是有向完全圖。
n
個頂點的有向完全圖有 $n(n-1) \over 2$ 條邊。
稠密圖和稀疏圖
邊或弧很少的圖是稀疏圖;邊或弧很多的圖是稠密圖。
它們都是相對概念。
權、網
與圖的邊或弧相關的數字叫做權(Weight)。
權可以表示一個頂點到另一個頂點的距離或耗費。
帶權的圖叫做網(Network)。
子圖
假設有兩個圖 $G = (V,\lbrace E \rbrace)$,$G' = (V',\lbrace E' \rbrace)$,$V' \subseteq V$,$E' \subseteq E$,那麼,G'
是 G
的子圖。
圖的頂點與邊間的關係(暫時不做筆記,有空再補充)
概念太多,記錄太麻煩,暫時不做筆記,有空再補充。
連通圖
連通圖
圖7-2-13圖2根據定義,我認爲它不是強連通圖。
數據結構_圖_連通圖
數據結構_圖_連通圖_2
數據結構_圖_連通圖_3
數據結構_圖_連通圖_4
生成樹
不理解。
數據結構_圖_生成樹_1
數據結構_圖_生成樹_2
有向樹
不理解。
數據結構_圖_有向樹_1
數據結構_圖_有向樹_2
圖的抽象數據類型
ADT 圖(Graph)
Data
頂點的有窮非空集合和邊的集合
Operation
CreateGraph(*G, V, VR):按照頂點集 V 和 邊弧集 VR 的定義構造圖 G。
DestroyGraph(*G):圖G存在則銷燬它。
LocateVex(G, u):若圖 G 中存在頂點 u,則返回它在圖中的位置。
GetVex(G, v):返回圖 G 中頂點 v 的值。
PutVex(G, v, value):將圖 G 中頂點 v 賦值 value。
FirstAdjVex(G, *v):返回頂點 v 的一個鄰接頂點,若頂點在 G 中無鄰接頂點則返回空。
NextAdjVex(G, v, *w):返回頂點 v 相對於頂點 w 的下一個鄰接頂點,若 w 是 v 的最後
一個鄰接點則返回“空”。
InsertVex(*G, v):在圖 G 中增添新頂點 v。
DeleteVex(*G, v): 刪除圖 G 中頂點 v 及其相關的弧。
InsertArc(*G, v, w):在圖中增添弧 <v,w>,若 G 是無向圖,還要增加對稱弧 <w,v>。
DeleteArc(*G, v, w):在圖中刪除弧 <v,w>,若 G 是無向圖,還需要刪除對稱弧 <w,v>。
DFSTraverse(G):對圖 G 深度優先遍歷,在遍歷過程中對每個頂點調用。
HFSTraverse(G):對圖 G 廣度優先遍歷,在遍歷過程中對每個頂點調用。
endADT
圖的存儲結構(難理解)
鄰接矩陣
定義
圖的鄰接矩陣(Adjacency Matrix)存儲方式是用兩個數組表示圖。一個一維數組存儲圖中頂點信息,一個
二維數組(稱爲鄰接矩陣)存儲圖中的邊或弧信息。
數據結構_圖_鄰接矩陣_定義
無向圖實例
數據結構_圖_鄰接矩陣_解釋
有向圖實例
數據結構_圖_鄰接矩陣_有向圖
網圖
數據結構_圖_鄰接矩陣_網圖定義
數據結構_圖_鄰接矩陣_網圖
鄰接矩陣存儲結構
鄰接矩陣存儲結構代碼:
備註:用65536代替 $\infty$
typedef char VertexType;
typedef int EdgeType;
#define MAXVEX 100; // 最大頂點數
#define INFINITY 65535;
typedef struct
{
VertexType vexs[MAXVEX]; // 頂點表
EdgeType arc[MAXVEX][MAXVEX]; // 鄰接矩陣,可看作邊表
int numVertexes, numEdges; // 圖中當前頂點數和邊數
}MGraph;
創建無向網圖代碼:
void CreateGraph(MGraph *G)
{
int i, j, k, w;
printf("%s", "輸入頂點數和邊數:\n");
scanf("%d, %d", &G->numVertexes, &G->numEdges); // 輸入頂點數和邊數
for(i = 0; i < G->numVertexes; i++){
scanf(&G->vexs[i]);
}
for(i = 0; i < G->numVertexes; i++){
for(j = 0; j < G->numVertexes; j++){
G->arc[i][j] = INFINITY; // 鄰接矩陣初始化
}
}
for(k = 0; k < G->numEdges; k++){ // 讀入numEdges條邊,建立鄰接矩陣
printf("輸入邊(vi,vj)上的下標i、下標j和權w:\n");
scanf("%d, %d, %d", &i, &j, &w); // 輸入邊(vi,vj)上的權w
G->arc[i][j] = w;
G->arc[j][i] = G->arc[i][j]; // 因爲是無向圖,矩陣對稱
}
}
鄰接表
概念
定義
數組與鏈表相結合的方法稱爲鄰接表(Adjacency List)。
思路
-
圖中頂點用一個一維數組存儲。該數組存儲兩部分內容:頂點信息和指針。
該指針指向該頂點的第一個鄰接點。 -
圖中的每個頂點 $v_i$ 的所有鄰接點構成一個線性表,用單鏈表存儲。
這個線性表可能叫 邊表,也可能叫 出邊表。
當圖爲無向圖的時候,這個線性表叫做頂點 $v_i$ 的邊表。
當圖爲有向圖的時候,這個線性表叫做頂點 $v_i$ 作爲弧尾的出邊表。
摘抄書本
無向圖的鄰接表結構
數據結構_圖_鄰接表_無向圖的鄰接表結構
$v_0$ 的鄰接點是 $v_1,v_2,v_3$,$v_0$的 firstEdge
存儲的指針指向它的第一個鄰接點 $v_1$。
$v_1$ 的 adjvex
存儲的是 該頂點在頂點表中的下標,next
存儲的指針指向 $v_0$ 的第二個鄰接點 $v_2$。
有向圖的鄰接表結構
數據結構_圖_鄰接表_有向圖的鄰接表結構_1
數據結構_圖_鄰接表_有向圖的鄰接表結構_2
網圖的鄰接表結構
數據結構_圖_鄰接表_網圖
鄰接表存儲結構(看不懂)
結點定義代碼
typedef char VertexType;
typedef int EdgeType;
typedef struct EdgeNode // 邊表結點
{
int adjvex; // 鄰接點域,存儲該頂點對應的下標
EdgeType weight; // 權值
struct EdgeNode *next; // 鏈域,指向下一個連接點
}EdgeNode;
typedef struct VertexNode // 頂點表結點
{
VertexType data; // 頂點域,存儲頂點信息
EdgeNode *firstEdge; // 邊表頭指針
}VertexNode, AdjList[MAXVEX];
typedef struct
{
AdjList adjList;
int numVertexes, numEdges; // 圖中當前頂點數和邊數
}GraphAdjList;
邊表結點 EdgeNode
爲什麼如此定義?
無向圖的鄰接表創建代碼
void CreateALGraph(GraphAdjList *G)
{
int i, j, k;
EdgeNode *e;
printf("輸入頂點數和邊數:\n");
scanf("%d, %d", &G->numVertexes, &G->numEdges); // 輸入頂點數和邊數
for(i = 0; i < G->numVertexes; i++){
scanf(&G->adjList[i].data); // 輸入頂點信息。這種寫法,有問題嗎?
G->adjList[i].firstEdge = NULL; // 將邊表置爲空表。爲什麼?
}
for(k = 0; k < G->numVertexes; i++){ // 建立邊表
printf("輸入邊(Vi,Vj)上的頂點序號:\n");
scanf("%d, %d", &i, &j); // 輸入邊(Vi,Vj)上的頂點序號
e = (EdgeNode *)malloc(sizeof(EdgeNode)); // 申請內存空間,創建邊表結點
e->adjvex = j; // 鄰接序號爲j
e->next = G->adjList[i].firstEdge; // 將e指針指向當前頂點指向的結點 ?
G->adjList[i].firstEdge = e; // 將當前頂點的指針指向e
e = (EdgeNode *)malloc(sizeof(EdgeNode)); // 申請內存空間,創建邊表結點
e->adjvex = i; // 鄰接序號爲i
e->next = G->adjList[j].firstEdge; // 將e指針指向當前頂點指向的結點
G->adjList[j].firstEdge = e; // 將當前頂點的指針指向e
}
}
十字鏈表(看不懂)
摘抄
數據結構_圖_十字鏈表_1
數據結構_圖_十字鏈表_2
數據結構_圖_十字鏈表_3
數據結構_圖_十字鏈表_4
鄰接多重表(看不懂)
摘抄
數據結構_圖_鄰接多重表_1
數據結構_圖_鄰接多重表_2
數據結構_圖_鄰接多重表_3
數據結構_圖_鄰接多重表_4
數據結構_圖_鄰接多重表_5
邊集數組
摘抄
數據結構_圖_邊集數組_1
數據結構_圖_邊集數組_2
圖的遍歷
定義
從圖中某一個頂點出發遍訪圖的其餘所有頂點,且使所有頂點被訪問且只訪問一次。
這個過程,就叫做圖的遍歷(Traversing Graph)。
深度優先遍歷(Depth_First_Search)
摘抄
數據結構_圖_深度優先遍歷_摘抄_1
虛線表示已經遍歷過的點,實線表示未遍歷過的點。
數據結構_圖_深度優先遍歷_摘抄_2
數據結構_圖_深度優先遍歷_摘抄_3
代碼
鄰接矩陣代碼
typedef int Boolean; // Boolean是布爾類型,其值是TRUE或FALSE
Boolean visited[MAX]; // 訪問標誌的數組
void DFS(MGraph G, int i)
{
int j;
visited[i] = TRUE;
printf("%c", G.vexs[i]);
/*2*/for(j = 0; j < G.numVertexes; j++){
/*1*/if(G.arc[i][j] == 1 && !visited[j] ){
DFS(G, j); // 對未訪問的頂點遞歸調用
}
}
}
void DFSTraverse(MGraph G)
{
int i;
for(i = 0; i < G.numVertexes; i++){
visited[i] = FALSE; // 初始所有頂點狀態都是未訪問過狀態
}
for(i = 0; i < G.numVertexes; i++){
if(!visited[i]){
DFS(G, i);
}
}
}
第1行,G.arc[i][j] == 1
表明 $v_iv_j$ 邊存在,這是根據鄰接圖矩陣定義得出的
結果。
第1行,visited[j]
不能理解這句。
第2行的循環裏面的遞歸,什麼時候終止?
鄰接表代碼(不懂)
void DFS(GraphAdjList GL, int i)
{
EdgeNode *p;
visited[i] = TRUE;
printf("%c", GL->adjList[i].data);
p = GL->adjList[i].firstEdge;
while(p){
if(!visited[p->adjvex]){
DFS(GL, p->adjvex);
}
p = p->next;
}
}
void DFSTraverse(GraphAdjList GL)
{
int i;
for(i = 0; i < GL->numVertexes; i++){
visited[i] = FALSE;
}
for(i = 0; i < GL->numVertexes; i++){
if(!visited[i]){
DFS(GL, i);
}
}
}
廣度優先遍歷(Breadth_First_Search)
摘抄
數據結構_圖_廣度優先遍歷_摘抄_1
數據結構_圖_廣度優先遍歷_摘抄_2
不明白如何得到這張圖。
代碼
鄰接矩陣代碼(不懂)
void BFSTraverse(MGraph G)
{
int i, j;
Queue Q;
for(i = 0; i < G.numVertexes; i++){
visited[i] = FALSE;
}
InitQueue(&Q); // 初始化一個輔助用的隊列
for(i = 0; i < G.numVertexes; i++){ // 對每一個頂點做循環
if(!visited[i]){
visited[i] = TRUE;
printf("%c", G.vexs[i]);
EnQueue(&Q, i); // 將此頂點入隊列
while(!QueueEmpty(Q)){
DeQueue(&Q, &i); // 將隊列中元素出隊列,賦值給i
for(j = 0; j < G.numVertexes; j++){
if(G.arc[i][j] == 1 && !visited[j]){
visited[j] = TRUE;
printf("%c", G.vexs[j]);
EnQueue(&Q, j);
}
}
}
}
}
}
鄰接表代碼(不懂)
void BFSTraverse(GraphAdjList GL)
{
int i;
EdgeNode *p;
Queue Q;
for(i = 0; i < GL->numVertexes; i++){
visited[i] = FALSE;
}
InitQueue(&Q);
for(i = 0; i < GL->numVertexes; i++){
if(!visited[i]){
visited[i] = TRUE;
printf("%c", GL->adjList[i].data);
EnQueue(&Q, i);
while(!QueueEmpty(Q)){
DeQueue(&Q, &i);
p = GL->adjList[i].firstEdge; // 找到當前頂點邊錶鏈表頭指針
while(p){
if(!visited[p->adjvex]){
visited[p->adjvex] = TRUE;
printf("%c", GL->adjList[p->adjvex].data);
EnQueue(&Q, p->adjvex);
}
}
p = p->next; // 指針指向下一個鄰接點
}
}
}
}
最小生成樹
定義(不懂)
把構造連通網的最小代價生成樹稱爲最小生成樹(Minimum Cost Spanning Tree)。
普里姆(Prim)算法(不懂)
代碼
void MiniSpanTree_Prim(MGraph G)
{
int min, i, j, k;
int adjvex[MAXVEX]; // 保存相關頂點下標
int lowcost[MAXVEX]; // 保存相關頂點間邊的權值
// 初始化第一個權值爲0,即v0加入生成樹
// lowcost的值爲0,在這裏就是此下標的頂點已經加入生成樹
lowcost[0] = 0;
adjvex[0] = 0; // 初始化第一個頂點下標爲0
for(i = 1; i < G.numVertexes; i++){
lowcost[i] = G.arc[0][i]; // 將v0頂點與之有邊的權值存入數組
adjvex[i] = 0; // 初始化都爲v0的下標
}
for(i = 1; i < G.numVertexes; i++){
min = INFINITY; // 初始化最小權值爲 INFINITY
j = 1; k = 0;
while(j < G.numVertexes){
if(lowcost[j] != 0 && lowcost[j] < min){
min = lowcost[j]; // 讓當前權值成爲最小值
k = j; // 將當前最小值的下標存入k
}
j++;
}
printf("(%d, %d)", adjvex[k], k); //打印當前頂點邊中權值最小的邊
lowcost[k] = 0; // 將當前頂點的權值設置爲0,表示此頂點已經完成任務
for(j = 1; j < G.numVertexes; j++){
if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j]){
lowcost[j] = G.arc[k][j]; // 將較小權值存入lowcost
adjvex[j] = k; // 將下標爲k的頂點存入adjvex
}
}
}
}
代碼解釋
數據結構_圖_普里姆算法_代碼解釋_8
數據結構_圖_普里姆算法_代碼解釋_1
數據結構_圖_普里姆算法_代碼解釋_2
數據結構_圖_普里姆算法_代碼解釋_3
數據結構_圖_普里姆算法_代碼解釋_4
數據結構_圖_普里姆算法_代碼解釋_5
數據結構_圖_普里姆算法_代碼解釋_6
數據結構_圖_普里姆算法_代碼解釋_7
克魯斯卡爾(Kruskal)算法
代碼
typedef struct
{
int begin;
int end;
int weight;
}Edge;
void MiniSpanTree_Kruskal(MGraph G)
{
int i, n, m;
Edge edges[MAXEDGE]; // 定義邊集數組
int parent[MAXVEX]; // 定義一數組用來判斷邊與邊是否形成環路
for(i = 0; i < G.numVertexes; i++){
parent[i] = 0; // 初始化數組值爲0
}
for(i = 0; i < G.numVertexes; i++){
n = Find(parent, edges[i].begin);
m = Find(parent, edges[i].end);
if(n != m){ // 假如n與m不等,說明此邊沒有與現有生成樹形成環路
// 將此邊的結尾頂點放入下標爲起點的parent中
// 表示此頂點已經在生成樹集合中
parent[n] = m;
printf("(%d, %d) %d", edges[i].begin, edges[i].end, edges[i].weight);
}
}
}
int Find(int *parent, int f) // 查找連線頂點的尾部下標
{
while(parent[f] > 0){
f = parent[f];
}
return f;
}
代碼解釋
數據結構_圖_克魯斯卡爾算法_代碼解釋_1
數據結構_圖_克魯斯卡爾算法_代碼解釋_2
數據結構_圖_克魯斯卡爾算法_代碼解釋_3
數據結構_圖_克魯斯卡爾算法_代碼解釋_4
數據結構_圖_克魯斯卡爾算法_代碼解釋_5
數據結構_圖_克魯斯卡爾算法_代碼解釋_6
數據結構_圖_克魯斯卡爾算法_代碼解釋_7
數據結構_圖_克魯斯卡爾算法_代碼解釋_8
數據結構_圖_克魯斯卡爾算法_代碼解釋_9
數據結構_圖_克魯斯卡爾算法_代碼解釋_10
數據結構_圖_克魯斯卡爾算法_代碼解釋_11
數據結構_圖_克魯斯卡爾算法_代碼解釋_12
最短路徑(看不懂)
暫時放棄
定義
非網圖的最短路徑,是指兩頂點之間經過的邊數最少的路徑。
網圖的最短路徑,是指兩頂點之間經過的邊上的權值之和最小的路徑。
路徑上的第一個源點,最後一個頂點是終點。
拓撲排序
關鍵路徑
作者:剛剛悟道
鏈接:https://www.jianshu.com/p/6cace353141d
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。