目錄node
有向圖算法
無向圖數組
簡單圖,圖G知足:數據結構
多重圖,非簡單圖即爲多重圖。spa
路徑,點 u 到 點 v 的路是,u,a,b,c,d,...,v 的一個點序列。
路徑長度,路徑上邊的個數。
迴路(環),路徑中,第一個點和最後一個點相同。
簡單路徑,路徑中,點序列不重複。
簡單迴路,迴路中,點序列不重複。
距離,點 u 到 點 v 的最短路徑。若不存在則路徑爲無窮大(∞)。指針
子圖,有兩個圖 G=(V,E) 和 G'=(V',E'),\(V'\in V,E'\in E\) 則 G' 是 G 的子圖。
生成子圖,子圖知足 V(G')=V(G)。code
生成樹,連通圖中包含全部點的一個極小連通子圖。blog
生成森林,非連通圖中全部連通份量的生成樹。排序
帶權圖(網),邊上有數值的圖。事件
徹底圖或簡單徹底圖,無向圖中,任意兩個點都存在邊。
連通,無向圖中,點 v 到 點 u 之間有路徑存在,則 v,w 是連通的。
連通圖,圖中任意兩點都連通。
連通份量,非連通圖中的極大連通子圖爲連通份量。
點的度,與該點相連邊的個數。記爲TD(V)。
有向徹底圖,在有向圖中,任意兩個點之間都存在方向相反的弧。
強連通、強連通圖、強連通份量,有向圖中與無向圖相對的概念。
出度,入度,出度爲是以點爲起點的弧的數量,記爲 ID(v)。入度是以點爲終點的弧的數量記爲 OD(v)。TD(v)=ID(v)+OD(v)。
鄰接矩陣即便用一個矩陣來記錄點與點之間的鏈接信息。
對於結點數爲 n 的圖 G=(V,E)的鄰接矩陣A 是 nxn 的矩陣。
對帶權圖而言,若頂點vi,vj相連則鄰接矩陣中存着該邊對應的權值,若不相連則用無窮大表示。
# define MAXSIZE typedef struct { int vexs [MAXSIZE]; int edges[MAXSIZE][MAXSIZE]; int vexnum, arcnum; // 點和邊的數量 }MGraph;
對每一個頂點創建一個單鏈表,而後全部頂點的單鏈表使用順序存儲。
頂點表由頂點域(data)和指向第一條鄰邊的指針(firstarc)構成。
邊表,由鄰接點域(adjvex)和指向下一條鄰接邊的指針域(nextarc)構成。
typedef struct ArcNode{ // 邊結點 int adjvex; // 邊指向的點 struct ArcNode *next; //指向的下一條邊 }ArcNode; typedef struct VNnode{ //頂點節點 int data; ArcNode *first; }VNode, AdjList[MAX] typedef struct { //鄰接表 AdjList vertices; int vexnum, arcnum; } ALGraph;
有向圖的一種表示方式。
十字鏈表中每一個弧和頂點都對應有一個結點。
typedef struct ArcNode{ int tailvex, headvex; struct ArcNode *hlink, *tlink; //InfoType info; } ArcNode; typedef struct VNode{ int data; ArcNode *firstin, *firstout; }VNode; typeder struct{ VNode xlist[MAX]; int vexnum, arcnum; } GLGrapha;
鄰接多重表是無向圖的一種鏈式存儲方式。
邊結點:
點結點:
鄰接多重表中,依附於同一點的邊串聯在同一鏈表中,因爲每條邊都依附於兩個點,因此每一個點會在邊中出現兩次。
typedef struct ArcNode{ bool mark; int ivex, jvex; struct ArcNode *ilink, *jlink; // InfoType info; }ArcNode; typedef struct VNode{ int data; ArcNode *firstedge; }VNode; typedef struct { VNode adjmulist[MAX]; int vexnum, arcnum; } AMLGraph;
Adjacent(G,x,y)
,判斷圖是否存在邊(x,y)或<x,y>。Neighbors
,列出圖中與 x 鄰接的邊。InsertVertex(G,x)
,在圖中插入頂點 x。DeleteVertex(G,x)
,在圖中刪除頂點 x。AddEdge(G,x,y)
,若是(x,y)或<x,y>不存在,則添加。RemoveEdge(G,x,y)
,若是(x,y)或<x,y>存在,則刪除。FirstNeighbor(G,x)
,求圖中頂點 x 的第一個鄰接點。存在返回頂點號,不存在返回-1。NextNeighbor(G,x,y)
,返回除x的的下一個鄰接點,不存在返回-1;GetEdgeValue(G,x,y)
,得到(x,y)或<x,y>的權值。SetEdgeValue(G,x,y)
,設置(x,y)或<x,y>的權值。廣度優先搜索(BFS)有點相似於二叉樹的層序遍歷算法。從某個頂點 v 開始遍歷與 v 鄰近的 w1,w2,3...,而後遍歷與 w1,w2,3...wi 鄰近的點。
因爲 BFS 是一種分層的搜索算法,因此必需要藉助一個輔助的空間。
//初始化操做 bool visited[MAX]; for(int i=0;i<G.vexnum;i++) visited[i]=FALSE; void BFSTraverse(Graph G){ InitQueue(Q); for(int i=0;i<G.vexnum;i++){ if(!visited[i]) BFS(G, i); } } void BFS(Graph G, int v){ visit(v); visited[v]=TRUE; Enqueue(Q,v); while(!isEmpty(Q)){ Dequeue(Q,v); for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){ if(!visited[w]){ visit[w]; visited[w]=TRUE; EnQueue(Q,w); } } } }
時間複雜度分析:
鄰接表:O(|V|+|E|)
鄰接矩陣:O(|V|^2)
//初始化操做 bool visited[MAX]; for(int v=0;v<G.vexnum;v++) visited[v]=FALSE; void DFSTraverse(Graph G){ for(int v=0;v<G.vexnum;v++){ if(!visited[v]) DFS(G,v); } } void DFS(Graph G,int v){ visit(v); visited[v]=TRUE; for(w=FistNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) if(!visited[w]) DFS(G,w) }
一個連通圖的生成樹是圖的極小連通子圖,即包含圖中全部頂點,且只包含儘量少的邊的樹。
對於一個帶權的連通圖,生成樹不一樣,對應的權值也不一樣,權值最小的那棵生成樹就是最小生成樹。
對於最小生成樹,有以下性質:
構造最小生成樹有多種算法,可是通常會用到如下性質:
若 G 是一個帶權連通無向圖,U 是 點集 V 的一個非空子集。若(u,v)其中 u∈U,v∈V-U,是一條具備最小權值的邊,則一定存在一棵包含邊(u,v)的最小生成樹。
通用算法以下:
MST(G){ T=NULL; while T未造成生成樹; do 找到一條最小代價邊(u,v)且加入 T 後不會產生迴路; T=T∪(u,v) }
Prim算法的執行很是相似於尋找圖最短路徑的Dijkstra算法。
從某個頂點出發遍歷選取周圍最短的邊。
//僞代碼描述 void Prim(G,T){ T=∅; U={w}; //w爲任意頂點 while((V-U)!=∅){ 找到(u,v),u∈U,v∈(V-U),且權值最小; T=T∪{(u,t)}; U=U∪{v} } }
以鄰接矩陣爲例:
void Prim(MGraph G) { int sum = 0; int cost[MAXSIZE]; int vexset[MAXSIZE]; for(int i=0;i<G.vexnum;i++) cost[i]=G.edges[0][i]; for(int i=0;i<G.vexnum;i++) vexset[i] = FALSE; vexset[0]=TRUE; for(int i=1;i<G.vexnum;i++) { int mincost=INF; int minvex; int curvex; for(int j=0;j<G.vexnum;j++) { if(vexset[j]==FALSE&&cost[j]<mincost) { mincost=cost[j]; minvex=j; } vexset[minvex]=TRUE; curvex = minvex; } sum+=mincost; for(int j=0;j<G.vexnum;j++) if(vexset[j]==FALSE&&G.edges[curvex][j]<cost[j]) cost[j]=G.edges[curvex][j] } }
Prim算法的複雜度爲O(|V|^2)不依賴於|E|,因此適合於邊稠密的圖。
構造過程:
kruskal所作的事情跟prim是反過來的,kruskal算法對邊進行排序,依次選出最短的邊連到頂點上。
//僞代碼描述 void Kruskal(V,T){ T=V; numS=n; //連通份量數 while(nums>1){ 從E選出權值最小的邊(v,u); if(v和u屬於T中不一樣的連通份量){ T=∪{(v,u)}; nums--; } } }
一樣以鄰接矩陣爲例。
typedef struct { int v1,v2; int w; } Road; Road road[MAXSIZE]; int v[MAXSIZE]; int getRoot(int x) { while(x!=v[x]) x=v[x]; return x; } void Kruskal(MGraph G, Road road[]) { int sum=0; for(int i=0;i<G.vexnum;i++) v[i]=i; sort(road,G.arcnum); for(int i=0;i<G.arcnum;i++) { int v1=getRoot(road[i].v1); int v2=getRoot(road[i].v2); if(v1!=v2) { v[v1]=v2; sum+=road[i].w; } } }
kruskal算法的複雜度爲O(|E|log|E|)適合邊少點多的圖。
構造過程:
最短路徑算法通常會利用最短路徑的一條性質,即:兩點間的最短路徑也包含了路徑上其餘頂點間的最短路徑。
Dijkstra 算法通常用於求單源最短路徑問題。即一個頂點到其餘頂點間的最短路徑。
這裏咱們須要用到三個輔助數組:
dist[vi]
,從 v0 到每一個頂點 vi 的最短路徑長度。path[vi]
,保存從 v0 到 vi 最短路徑上的前一個頂點。set[]
,標記點是否被併入最短路徑。執行過程:
結合圖來理解就是:
void Dijkstra(MGraph G, int v) { int set[MAXSIZE]; int dist[MAXSIZE]; int path[MAXSIZE]; int min; int curvex; for(int i=0;i<G.vexnum;i++) { dist[i]=G.edges[v][i]; set[i]=FALSE; if(G.edges[v][i]<INF) path[i]=v; else path[i]=-1; } set[v]=TRUE;path[v]=-1; for(int i=0;i<G.vexnum-1;i++) { min=INF; for(int j=0;j<G.vexnum;j++) { if(set[j]==FALSE;&&dist[j]<min) { curvex=j; min=dist[j]; } set[curvex]=TRUE; } for(int j=0;j<G.vexnum;j++) { if(set[j]==FALSE&&(dist[curvex]+G.edges[curvex][j])<dist[j]) { dist[j]=dist[u]+G.edges[curvex][j]; path[j]=curvex; } } } }
複雜度分析:從代碼能夠很容易看出來這裏有兩層的for循環,時間複雜度爲O(n^2)。
適用性:不適用於帶有負權值的圖。
floyd算法是求圖中任意兩個頂點間的最短距離。
過程:
\(A^{(k)}\)矩陣存儲了前K個節點之間的最短路徑,基於最短路徑的性質,第K輪迭代的時候會求出第K個節點到其餘K-1個節點的最短路徑。
圖解:
void Floyd(MGraph G, int Path[][MAXSIZE]) { int A[MAXSIZE][MAXSIZE]; for(int i=0;i<G.vexnum;i++) for(int j=0;j<G.vexnum;j++) { A[i][j]=G.edges[i][j]; Path[i][j]=-1; } for(int k=0;k<G.vexnum;k++) for(int i=0;i<G.vexnum;i++) for(int j=0;j<G.vexnum;j++) if(A[i][j]>A[i][k]+A[k][j]) { A[i][j]=A[i][k]+A[k][j]; Path[i][j]=k; } }
複雜度分析:主循環爲三個for,O(n^3)。
適用性分析:容許圖帶有負權邊,可是不能有負權邊構成的迴路。
一種比較經常使用的拓撲排序算法:
最終獲得的拓撲排序結果爲:1,2,4,3,5。
在帶權有向圖中,若權值表示活動開銷則爲AOE網。
AOE網的性質:
源點:AOE 中僅有一個入度爲0的頂點。
匯點:AOE 中僅有一個出度爲0的頂點。
關鍵路徑:從源點到匯點的全部路徑中路徑長度最大的。
關鍵路徑長度:完成整個工程的最短時間。
關鍵活動:關鍵路徑上的活動。
先定義幾個量:
ve(k)
,事件 vk 最先發生時間。決定了全部從 vj 開始的活動能開工的最先時間。
vl(k)
,事件 vk 最遲發生的時間。保證所指向的事件 vi 能在 ve(i)以前完成。
e(i)
,活動 ai 最先開始的時間。
l(i)
,活動 ai 最遲開始時間。
d(i)
,活動完成的時間餘量。
求關鍵路徑算法以下:
能夠求得關鍵路徑爲(v1,v3,v4,v6)