目录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)