06 圖
定義
圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示爲:G(V,E),其中,G表示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合。
1. 在圖中數據元素,我們稱之爲頂點(Vertex)
2. 在圖中,任意兩個頂點之間都可能有關係,頂點之間的邏輯關係用邊來表示,邊集可以是空的。
-
各種圖
- 無向邊:
若頂點vI到vj之間的邊沒有方向,則稱這條邊爲無向邊(Edge),用無序偶對(vI,vj)來表示。如果圖中任意兩個頂點之間的邊都是無向邊,則稱該圖爲無向圖(Undirected graphs)。
- 有向邊:
若從頂點vI到vj的邊有方向,則稱這條邊爲有向邊,也稱爲弧(Arc)。如果圖中任意兩個頂點之間的邊都是有向邊,則稱該圖爲有向圖(Directed graphs)
- 無向完全圖:
在無向圖中,如果任意兩個頂點之間都存在邊,則稱該圖爲無向完全圖
- 有向完全圖
在有向圖中,如果任意兩個頂點之間都存在方向互爲相反的兩條弧,則稱該圖爲有向完全圖。
- 權
有些圖的邊或弧具有與它相關的數字,這種與圖的邊或弧相關的數叫做權(Weight)。這些權可以表示從一個頂點到另一個頂點的距離或耗費。這種帶權的圖通常稱爲網(Network)
-
圖的頂點與邊間關係
- 路徑的長度:
是路徑上的邊或弧的數目
-
連通圖相關術語
- 連通圖:
在無向圖G中,如果從頂點v到頂點v’有路徑,則稱v和v’是連通圖。如果對於圖中任意兩個頂點vi、vj屬於V,vi和vj都是連通的,則稱G是連通圖(Connected Graph )
- 強連通圖:
在有向圖G中,如果對於每一對vi、vj屬於V、vi不等於vj,從vi到vj和從vj到vi都存在路徑,則稱G是強連通圖。有向圖中的極大強連通子圖稱做有向圖的強連通分量。
-
圖的定義與術語總結
圖的存儲結構
ADT圖(Graph)
Data
頂點的有窮非空集合和邊的集合
Operation
CeateGraph(*G, V, VR):按照頂點集V和邊弧集VR的定義構造圖G
DestroyGraph(*G):圖G存在則銷燬
LocateVex(G, u):若圖G中存在頂點u,則返回圖中的位置
PutVex(G, v, value):將圖G中的頂點v賦值value
FirstAdjVex(G, *v):返回頂點v的一個鄰接頂點,若頂點在G中無鄰接頂點返回空
NextAdjVex(G, v, *w):返回頂點v相對於頂點w的下一個鄰接頂點,若w是v的最後一個鄰接點則返回“空”
InsertVex(*G, v):在圖中增添新頂點v
DeleteVex(*G, v):刪除圖G中頂點v及其相關的弧
InsertArc(*G, v, w):在圖G中增添弧<v,w>,若G是無向圖,還需要增添對稱弧<w,v>。
DeleteArc(*G, v, w):在圖G中刪除弧<v,w>,若G是無向圖,則還刪除對稱弧<w,v>
DFSTraverse(G):對圖G中進行深度優先遍歷,在遍歷過程對每個頂點調用
HFSTraverse(G):在圖G中進行廣度優先遍歷,在遍歷過程對每個頂點調用。
endADT
-
鄰接矩陣(Adjacency Matrix)
圖的鄰接矩陣存儲方式使用兩個數組來表示圖。一個以爲數組存儲圖中頂點信息,一個二維數組(稱爲鄰接矩陣)存儲圖中的邊或弧的信息。
#define MAXVEX 100 /* 最大頂點數,應由用戶定義 */ #define INFINITY 65535 typedef int Status; /* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */ typedef char VertexType; /* 頂點類型應由用戶定義 */ typedef int EdgeType; /* 邊上的權值類型應由用戶定義 */ typedef struct { VertexType vexs[MAXVEX]; /* 頂點表 */ EdgeType arc[MAXVEX][MAXVEX];/* 鄰接矩陣,可看作邊表 */ int numNodes, numEdges; /* 圖中當前的頂點數和邊數 */ }MGraph;
構造鄰接矩陣:時間複雜度:O(n2)
/* 建立無向網圖的鄰接矩陣表示 */ void CreateMGraph(MGraph *G) { int i,j,k,w; printf("輸入頂點數和邊數:\n"); scanf("%d,%d",&G->numNodes,&G->numEdges); /* 輸入頂點數和邊數 */ for(i = 0;i <G->numNodes;i++) /* 讀入頂點信息,建立頂點表 */ scanf(&G->vexs[i]); for(i = 0;i <G->numNodes;i++) for(j = 0;j <G->numNodes;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)
鄰接表的結點定義
typedef char VertexType; /* 頂點類型應由用戶定義 */ typedef int EdgeType; /* 邊上的權值類型應由用戶定義 */ typedef struct EdgeNode /* 邊表結點 */ { int adjvex; /* 鄰接點域,存儲該頂點對應的下標 */ EdgeType info; /* 用於存儲權值,對於非網圖可以不需要 */ struct EdgeNode *next; /* 鏈域,指向下一個鄰接點 */ }EdgeNode; typedef struct VertexNode /* 頂點表結點 */ { VertexType data; /* 頂點域,存儲頂點信息 */ EdgeNode *firstedge;/* 邊表頭指針 */ }VertexNode, AdjList[MAXVEX]; typedef struct { AdjList adjList; int numNodes,numEdges; /* 圖中當前頂點數和邊數 */ }GraphAdjList;
鄰接表的創建:對於n個頂點和e條邊來說,本算法的時間複雜度爲O(n+e)
/* 建立圖的鄰接表結構 */ void CreateALGraph(GraphAdjList *G) { int i,j,k; EdgeNode *e; printf("輸入頂點數和邊數:\n"); scanf("%d,%d",&G->numNodes,&G->numEdges); /* 輸入頂點數和邊數 */ for(i = 0;i < G->numNodes;i++) /* 讀入頂點信息,建立頂點表 */ { scanf(&G->adjList[i].data); /* 輸入頂點信息 */ G->adjList[i].firstedge=NULL; /* 將邊表置爲空表 */ } for(k = 0;k < G->numEdges;k++)/* 建立邊表 */ { 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 */ } }
-
十字鏈表(Orthogonal List)
把鄰接表和逆鄰接表結合起來,解決展示有向圖的問題,就有了十字鏈表
-
鄰接多重表
-
邊集數組
圖的遍歷
從圖中某一頂點出發訪遍圖中其餘頂點,且使每一個頂點僅被訪問一次,這一過程就叫做圖的遍歷(Traversing Graph)
-
深度優先遍歷
深度優先遍歷(Depth_First_Search),也有稱爲深度優先搜索,簡稱爲DFS。深度優先遍歷其實就是一個遞歸的過程,其實也是一個樹的前序遍歷過程。
鄰接矩陣的深度優先遍歷
/* 鄰接矩陣的深度優先遞歸算法 */ void DFS(MGraph G, int i) { int j; visited[i] = TRUE; printf("%c ", G.vexs[i]);/* 打印頂點,也可以其它操作 */ for(j = 0; j < G.numVertexes; j++) 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,若是連通圖,只會執行一次 */ DFS(G, i); }
鄰接表的深度優先遍歷
/* 鄰接表的深度優先遞歸算法 */ 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,若是連通圖,只會執行一次 */ DFS(GL, i); }
總結:對於兩個不同存儲結構的深度優先遍歷算法,對於n個頂點e條邊的圖來說,鄰接矩陣由於是二維數組,要查找每個頂點的鄰接點需要訪問矩陣中的所有元素,因此都需要O(n2)的時間。而鄰接表做存儲結構時,找鄰接點所需要的時間取決於頂點和邊的數量,所以是O(n+e)。顯然對於點多邊少的稀疏圖來說,鄰接表的結構使得算法在時間效率上大大提高。
-
廣度優先遍歷
廣度優先遍歷(Breadth_First_Search),又稱爲廣度優先搜索,簡稱BFS。
鄰接矩陣的廣度優先遍歷
/* 鄰接矩陣的廣度遍歷算法 */ 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 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,若是連通圖,只會執行一次 */ DFS(GL, i); }
最小生成樹
我們把構造連通網的最小代價生成樹稱爲最小生成樹(Minimum Cost Spanning Tree)
-
普利姆(Prim)算法
普利姆算法生成最小生成樹
/* Prim算法生成最小生成樹 */ void MiniSpanTree_Prim(MGraph G) { int min, i, j, k; int adjvex[MAXVEX]; /* 保存相關頂點下標 */ int lowcost[MAXVEX]; /* 保存相關頂點間邊的權值 */ lowcost[0] = 0;/* 初始化第一個權值爲0,即v0加入生成樹 */ /* lowcost的值爲0,在這裏就是此下標的頂點已經加入生成樹 */ adjvex[0] = 0; /* 初始化第一個頂點下標爲0 */ for(i = 1; i < G.numVertexes; i++) /* 循環除下標爲0外的全部頂點 */ { lowcost[i] = G.arc[0][i]; /* 將v0頂點與之有邊的權值存入數組 */ adjvex[i] = 0; /* 初始化都爲v0的下標 */ } for(i = 1; i < G.numVertexes; i++) { min = INFINITY; /* 初始化最小權值爲∞, */ /* 通常設置爲不可能的大數字如32767、65535等 */ j = 1;k = 0; while(j < G.numVertexes) /* 循環全部頂點 */ { if(lowcost[j]!=0 && lowcost[j] < min)/* 如果權值不爲0且權值小於min */ { min = lowcost[j]; /* 則讓當前權值成爲最小值 */ k = j; /* 將當前最小值的下標存入k */ } j++; } printf("(%d, %d)\n", adjvex[k], k);/* 打印當前頂點邊中權值最小的邊 */ lowcost[k] = 0;/* 將當前頂點的權值設置爲0,表示此頂點已經完成任務 */ for(j = 1; j < G.numVertexes; j++) /* 循環所有頂點 */ { if(lowcost[j]!=0 && G.arc[k][j] < lowcost[j]) {/* 如果下標爲k頂點各邊權值小於此前這些頂點未被加入生成樹權值 */ lowcost[j] = G.arc[k][j];/* 將較小的權值存入lowcost相應位置 */ adjvex[j] = k; /* 將下標爲k的頂點存入adjvex */ } } } }
-
克魯斯卡爾(Kruskal)算法
最短路徑
- 迪斯杰特拉(Dijkstra)算法
- 佛洛依德(Floyd)算法
拓撲排序
- 介紹
- 算法
關鍵路徑
- 原理
- 算法