图
1.图的存储结构
数组表示法:用两个数组分别存储数据元素(顶点)的信息和数据元素之间的关系(边或弧)的信息。
1 #define INFINITY INT_MAX //最大值 2 #define MAX_VERTEX_NUM 20 //最大顶点数 3 typedef enum{ DG,DN,UDG,UDN } GraphKind; //有向图,有向网,无向图,无向网 4 typedef struct ArcCell{ 5 VRType adj; //VRType是顶点关系类型。对无权图,用0或1表示相邻否;对有权图,则为权值类型 6 InfoType *info; //该弧相关信息的指针 7 }ArcCell, AdjMatrix[MAX_VERTEX_MAX][MAX_VERTEX_MAX]; 8 typedef struct{ 9 VertexType vexs[MAX_VERTEX_NUM]; //顶点向量 10 AdjMatrix arcs; //邻接矩阵 11 int vexnum,arcnum; //图的当前顶点数和弧数 12 GraphKind kind; //图的种类标志 13 }MGraph;
以二维数组表示有n个顶点的图时,需存放n个顶点信息和n^2个弧信息的存储量。
对于无向图,顶点vi的度是邻接矩阵中第i行的元素之和。
对于有向图,第i行的元素之和为顶点vi的出度OD(vi),第j列的元素之和为顶点vj的入度ID(vj)。
1 Status CreateGraph(MGraph &G){ 2 scanf(&G.kind); 3 switch(G.kind){ 4 case DG: return CreateDG(G); //构造有向图G 5 case DN: return CreateDN(G); //构造有向网G 6 case UDG: return CreateUDG(G); //构造无向图G 7 case UDN: return CreateUDN(G); //构造无向网G 8 } 9 } 10 11 Status CreateUDN(MGraph &G){ 12 scanf(&G.vexnum,&G.arcnum,&IncInfo); 13 //构造顶点向量 14 //初始化邻接矩阵 15 //构造邻接矩阵 16 //输入一条边依附的顶点和权值 17 //确定v1和v2在G中的位置 18 //弧<v1,v2>的权值 19 //若弧含有相关信息,则输入 20 //置<v1,v2>对称弧<v2,v1> 21 }
链式表示法:邻接表,十字链表,邻接多重表
邻接表:在邻接表中,对图中每个顶点建立一个单链表,单链表中的结点表示依附于该顶点的边,对有向图是当前顶点指向结点的弧。
每个结点由3个域组成,邻接点域,链域,数据域。
邻接点域:指示与当前顶点邻接的点在图中的位置。
链域:指示单链表中的下一个结点。
数据域:存储和边或弧相关的信息,如权值等。
每个链表上附设一个头结点,指示单链表中的第一个结点,同时存储顶点的相关信息。
1 #define MAX_VERTEX_NUM 20 2 typedef struct ArcNode{ 3 int adjvex; //该弧指向的顶点的位置 4 struct ArcNode *nextarc; //指向下一条弧的指针 5 InfoType *info; //该弧相关信息的指针 6 }ArcNode; 7 8 typedef struct VNode{ 9 VertexType data; //顶点信息 10 ArcNode *firstarc; //指向第一条依附该顶点的弧的指针 11 }VNode,AdjList[MAX_VERTEX_NUM]; 12 13 typedef struct{ 14 AdjList vertices; 15 int vexnum,arcnum; //图的当前顶点数和弧数 16 int kind; //图的种类标志 17 }ALGraph;
十字链表:
十字链表是有向图的另一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。
在十字链表中,对应于有向图中每一条弧有一个结点,对应于每个顶点也有一个结点。结点的结构如下:
弧结点: tailvex,headvex,hlink,tlink,info
顶点结点:data,firstin,firstout
1 #define MAX_VERTEX_NUM 20 2 typedef struct ArcBox{ 3 int tailvex,headvex; 4 struct ArcBox *hlink,*tlink; 5 InfoType *info; 6 }ArcBox; 7 8 typedef struct VexNode{ 9 VertexType data; 10 ArcBox *firstin,*firstout; 11 }VexNode; 12 13 typedef struct{ 14 VexNode xlist[MAX_VERTEX_NUM]; 15 int vexnum,arcnum; 16 }OLGraph;
邻接多重表:邻接多重表是无向图的另一种链式存储结构。
邻接多重表的每一条边用一个结点表示,每一个顶点也用一个结点表示。
边结点:mark,ivex,ilink,jvex,jlink,info
顶点结点:data,firstedge
#define MAX_VERTEX_NUM 20 typedef enum{unvisited,visited}VisitIf; typedef struct EBox{ VisitIf mark; int ivex,jvex; struct EBox *ilink,*jlink; InfoType *info; }EBox; typedef struct VexBox{ VertexType data; EBox *firstedge; }VexBox; typedef struct{ VexBox adjmulist[MAX_VERTEX_NUM]; int vexnum,edgenum; }AMLGraphy;
2.图的遍历
从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次。
通常有两条遍历图的路径:深度优先搜索和广度优先搜索。,它们对有向图和无向图都适用。
深度优先搜索:
深度优先搜索遍历类似于树的先根遍历,是树的先根遍历的推广。
先访问顶点,然后访问一个邻接点,接着访问邻接点下一个邻接点,以此类推。
1 Bool visited[MAX]; 2 Status (*VisitFunc)(int v); 3 4 void DFSTraverse(Graph G,Status (*Visit)(int v)){ 5 VisitFunc=Visit; 6 for(v=0;v<G.vexnum;++v) visited[v]=FALSE; 7 for(v=0;v<G.vexnum;++v) 8 if(!visited[v]) DFS(G,v); 9 } 10 11 void DFS(Graph G,int v){ 12 visited[v]=TRUE; 13 VisitFunc(v); 14 for(w=FirstAdjVex(G,v);w >= 0;w=NextAdjVex(G,v,w)) 15 if(!visited[w]) 16 DFS(G,w); 17 }
二维数组表示邻接矩阵作图的存储结构时,查找每个邻接点所需时间为O(n^2)
当用邻接表作存储结构时,查找每个邻接点所需时间为O(e),遍历图的时间复杂度为O(n+e)
广度优先搜索算法
广度优先搜索遍历类似于树的按层次遍历的过程。
先访问顶点,然后依次访问各个邻接点,以此类推。
1 void BFSTraverse(Graph G,Status (*Visit)(int v)){ 2 for(v=0;v<G.vexnum;++v) visited[v]=FALSE; 3 InitQueue(Q); 4 for(v=0;v<G.vexnum;++v){ 5 if(!visited[v]){ 6 visited[v]=TRUE; 7 Visit(v); 8 EnQueue(Q,v); 9 while(!QueueEmpty(Q)){ 10 DeQueue(Q,u); 11 for(w=FirstAdjVex(G,u); w>=0; w=NextAdjVex(G,u,w)){ 12 if(!Visited[w]){ 13 Visited[w]=TRUE;Visit(w); EnQueue(Q,w); 14 } 15 } 16 } 17 } 18 } 19 }
深度优先搜索算法和广度优先搜索算法的时间复杂度相同,两者不同之处仅在于对顶点的访问顺序不同。
3.图的连通性问题
无向图的连通分量和生成树
有向图的强连通分量
最小生成树:多数算法利用了最小生成树的下列一种简称为MST的性质:假设N=(V,{E})是一个连通网,U是顶点集V的一个非空子集。,
若(u,v)是一条具有最小权值的边,其中u属于U,v属于V-U,则必存在一棵包含边(u,v)的最小生成树。
利用普里姆算法求最小生成树:
struct{ VertexType adjvex; VRType lowcost; }closedge[MAX_VERTEX_NUM]; void MiniSpanTree_PRIM(MGraph G,VertexType u){ //用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边。 //记录从顶点集U到V-U的代价最小的边的辅助数组定义:closedge[MAX_VERTEX_NUM] k=locate(G,u); for(j=0;j < G.vexnum;++j) if( j != k ) closedge[j]={u,G.arcs[k][j].adj}; //{adjvex,lowcost} closedge[k].lowcost=0; for(i=1;i < G.vexnum; ++i){ k=minimum(closedge); printf(closedge[k].adjvex,G.vexs[k]); closedge[k].lowcost=0; for(j=0;j<G.vexnum;++j){ if(G.arcs[k][j].adj < closedge[j].lowcost) closedge[j]={G.vexs[k],G.arcs[k][j].adj} } } }
普里姆算法的时间复杂度为O(n^2),与网中的顶点相关,与网中的边数无关,因此适用于求边稠密的网的最小生成树。
而克鲁斯卡尔算法则完全相反,它的时间复杂度为O(eloge)(e为网中的边数),因此适用于求边稀疏的网的最小生成树。
克鲁斯卡尔算法过程:假设连通图N=(V,{E}),则令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通
分量。在E中选择权值最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边而选择下一条最小权值的边,
依次类推,直至T中所有顶点都在同一连通分量上为止。
4.关节点和重连通分量
关节点:假若在删去顶点v以及和v相关联的各边之后,将图的一个连通分量分割成两个或两个以上的连通分量,则称顶点v为该图的一个关节点。
重连通图:一个没有关节点的连通图称为是重连通图。
若在连通图上至少删去k个顶点才能破坏图的连通性,则称此图的连通度为k。
利用深度优先搜索便可求得图的关节点,并由此可判别图是否是重连通的。
5.有向无环图及其应用
一个无环的有向图称作有向无环图,简称DAG图。
拓扑排序:由某个集合上的一个 偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
偏序:若集合X上的关系R是自反的,反对称的和传递的,则称R是集合X上的偏序关系。
全序:设R是集合X上的偏序,如果对每个x,y属于X必有xRy或yRx,则称R是集合X上的全序关系。
https://blog.csdn.net/liuchuo/article/details/51986226
由偏序定义得到拓扑有序的操作称为拓扑排序。
用顶点表示活动,用弧表示活动间的优先关系的有向图称为顶点表示活动的网,简称AOV-网。
在网中,若从顶点i到顶点j有一条有向路径,则i是j的前驱,j是i的后继。若<i,j>是网中一条弧,则i是j的直接前驱,j是i的直接后继。
在AVO网中不应该出现有向环。
如何进行拓扑排序:
1.在有向图中选一个没有前驱的顶点并输出之
2.从图中删除该顶点和所有以它为尾的弧
3.重复上述两步,直至全部顶点均已输出,否则说明当前有向图中存在环。
拓扑排序的算法:
1 Status TopologicalSort(ALGraph G){ 2 FindInDegree(G,indegree); //对各顶点求入度indegree[0..vernum-1] 3 InitStack(S); 4 for(i=0;i<G.vexnum;++i){ 5 if(!indegree[i])Push(S,i); //入度为0顶点入栈 6 } 7 count=0; 8 while(!StackEmpty(S)){ 9 Pop(S,i); 10 printf(i,G.vertices[i].data); //输出i号顶点并计数 11 ++count; 12 for(p=G.vertices[i].firstarc;p;p=p->nextarc){ 13 k=p->adjvex; 14 if(!(--indegree[k])) 15 Push(S,k); 16 } 17 } 18 if(count < G.vexnum) return ERROR; 19 else 20 return OK; 21 }
对有n个顶点和e条弧的有向图,建立求各顶点的入度的时间复杂度为O(e),建零入度顶点栈的时间复杂度为O(n),总的时间复杂度为O(n+e)
关键路径:
与AOV-网相对应的是AOE-网即边表示活动的网。AOE-网是一个带权的有向无环图,其中顶点表示事件,弧表示活动,权表示活动持续的时间。通常AOE-网可用来估算工程 的时间。
从开始点到完成点的最长路径的长度(这里路径长度指路径上各活动持续时间之和),路径长度最长的路径叫做关键路径。
最短路径:
路径上第一个顶点为源点,最后一个顶点为终点。
从某个源点到其余各顶点的最短路径:
迪杰斯特拉算法:
1.假设用带权的邻接矩阵arcs来表示带权有向图,arcs[i][j]表示弧<vi,vj>上的权值。
若<vi,vj>不存在,则置arcs[i][j]为无穷大。S为已找到从v出发的最短路径的终点的集合,它的初始状态为空集。
那么从v出发到图上各顶点(终点)vi可能达到的最短路径长度的初值为:
D[i]=arcs[Locate Vex(G,v)][i] vi属于V
2.选择vj,使得
D[j]=Min{D[i] | vi属于V-S}
vj就是当前求得的一条从v出发的最短路径的终点。令
S=S U {j}
3.修改从v出发到集合V-S上任一顶点vk可达的最短路径长度。如果:
D[j]+arcs[j][k] < D[k] 则修改D[k]为 D[k]=D[j]+arcs[j][k]
4.重复操作2,3共n-1次。即得到源点到终点最短路径序列。
void ShortesPath_DIJ(MGraph G,int v0,PathMatrix &P,ShortPathTable &D){ //用Dijkstra算法求有向网G的v0顶点到其余顶点v的最短路径P[v]及其带权长度D[v] //若P[V][W]为TRUE,则w是从v0到v当前求得最短路径上的顶点。 //final[v]为TRUE当且仅当v属于S,即已经求得从V0到V的最短路径 for(v=0;v < G.vexnum;++v){ final[v]=FALSE; D[v]=G.arcs[v0][v]; for(w=0;w < G.vexnum;++w) P[v][w]=FALSE; if(D[v] < INFINITY ) { //INFINITY =无穷大 P[v][v0]=TRUE; P[v][v]=TRUE; } } D[v0]=0;final[v0]=TRUE; for(i=1;i<G.vexnum;++i){ min=INFINITY; for(w=0;w<G.vexnum;++w) if(!final[w]) if(D[w] < min){ v=w; min=D[w]; } final[v]=TRUE; for(w=0;w<G.vexnum;++w) if(!final[w] && (min + G.arcs[v][w]) < D[w] ){ D[w]=min+G.arcs[v][w]; P[w]=P[v];//P[w]=P[v]+[w] P[w][w]=TRUE; } } }
该迪杰斯特拉算法复杂度为O(n^2)。
每一对顶点之间的最短路径:
有两种方法:
1.每次以一个顶点为源点,重复执行迪杰斯特拉算法n次。
2.佛洛伊德算法:
void Shortest_FLOYD(MGraph G,PathMatrix &P[],DistancMatrix &D){ //用Floyd算法求有向网G中各对顶点v和w之间的最短路径P[v][w] //及其带权长度D[v][w].若P[v][w][u]为TRUE,则u是从v到w当前求得 //最短路径上的顶点 for(v=0;v<G.vexnum;++v){ for(w=0;w<G.vexnum;++w){ D[v][w]=G.arcs[v][w]; for(u=0;u<G.vexnum;++u){ P[v][w][u]=FALSE; } if(D[v][w] < INFINITY) //从v到w有直接路径 P[v][w][v]=TRUE; P[v][w][w]=TRUE; } for(u=0;u<G.vexnum;++u){ for(v=0;v<G.vexnum;++v){ for(w=0;w<G.vexnum;++w){ if(D[v][u] + D[u][w] < D[v][w]){ //从v经u到w的一条路径更短 D[v][w]=D[v][u]+D[v][w]; for(i=0;i<G.vexnum;++i){ P[v][w][i]=P[v][u][i] || P[u][w][i]; } } } } } } }