图
- 图的定义和术语
-
G=(V,E),V(Vertex)为顶点集,E(Edge)为边集,边是无向的(),弧是有向的<>
-
有向图和无向图
-
完全图:任意两个顶点之间都有边相连
-
有向完全图
-
无向完全图
-
-
稀疏图:很少边或弧的图(边的数目小于定点*log定点数)
-
稠密图:和稀疏图相反
-
网:边或弧带有权值的图
-
邻接:有边或弧相连的两个顶点
-
顶点的度:和顶点相关联的边的数目,记为TD(v)
-
有向图的入度:以该顶点为终点的弧的数目,记为ID(v)
有向图的出度:以该顶点为起点的弧的数目,记为OD(v) -
连通图:任意两个顶点之间都有路径相连的图
-
生成树:连通图的极小连通子图,包含所有顶点,且只有n-1条边
-
极小连通子图:删除任意一条边都会使图不连通
-
生成森林:对非连通图,生成树森林
-
- 图的存储结构
-
邻接矩阵(数组)表示法
-
建立一个顶点表(记录各个顶点信息)和邻接矩阵(表示各个顶点之间关系)
-
无向图的邻接矩阵是对称的
-
有向图的邻接矩阵
-
网的邻接矩阵
-
邻接矩阵的存储表示:用两个数组分别存储顶点表和邻接矩阵
实现:#define MaxVertexNum 100 //顶点数目最大值 typedef char VertexType; //顶点的数据类型 typedef int ArcType; //带权图中边上权值的数据类型 typedef struct{ VertexType vexs[MaxVertexNum]; //顶点表 ArcType arcs[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表 int vexnum,arcnum; //图的当前顶点数和弧数 }AMGraph;
-
邻接矩阵法创建无向网
实现:status CreateUDN(AMGraph &G){ //输入顶点数和边数 cin>>G.vexnum>>G.arcnum; //输入顶点信息 for(int i=0;i<G.vexnum;i++){ cin>>G.vexs[i]; } //初始化邻接矩阵,所有顶点之间无边相连,用MaxInt表示 for(int i=0;i<G.vexnum;i++){ for(int j=0;j<G.vexnum;j++){ G.arcs[i][j]=MaxInt; } } //输入边的信息,建立邻接矩阵 for(int k=0;k<G.arcnum;k++){ cin>>v1>>v2>>w; //找到顶点v1和v2在图中的位置 i=LocateVex(G,v1);j=LocateVex(G,v2); //在邻接矩阵中添加边的信息 G.arcs[i][j]=w; //无向图是对称的,所以还需要添加对称的边 G.arcs[j][i]=G.arcs[i][j]; } return OK; } int LocateVex(AMGraph G,VertexType v){ //在图G中查找顶点v的位置 for(int i=0;i<G.vexnum;i++){ if(G.vexs[i]==v){ return i; } } return -1; }
-
邻接矩阵的优点:简单,直观,对于稠密图效率高
-
邻接矩阵的缺点:对于稀疏图,空间浪费严重,时间效率低
-
-
链式邻接表表示法
-
无向图
-
有向图
-
图的邻接表存储表示
- 顶点的存储结构:
typedef struct VNode{ VertexType data; //顶点信息 ArcNode *firstarc; //指向第一条依附该顶点的弧 }VNode,AdjList[MaxNum];
说明:Adjlist v等同于VNode v[MaxNum]
- 边的存储结构:
typedef struct ArcNode{ int adjvex; //该弧所指向的顶点的位置 struct ArcNode *nextarc; //指向下一条弧的指针 //对于带权图,还可以增加一个权值域 OtherInfo info;//和边相关的信息 }ArcNode;
- 图的存储结构:
typedef struct{ AdjList vertices; //邻接表 int vexnum,arcnum; //图的顶点数和弧数 }ALGraph;
- 顶点的存储结构:
-
邻接表法创建无向网
算法思想:实现:
status CreateUDG(ALGraph &G){ //输入顶点数和边数 cin>>G.vexnum>>G.arcnum; //输入顶点信息,建立顶点表 for(int i=0;i<G.vexnum;i++){ cin>>G.vertices[i].data; G.vertices[i].firstarc=NULL; } //输入边的信息,建立邻接表 for(int k=0;k<G.arcnum;k++){ cin>>v1>>v2//>>w; //输入边(v1,v2)的权值 //找到顶点v1和v2在图中的位置 i=LocateVex(G,v1); j=LocateVex(G,v2); //生成一个新的边结点 p1=(ArcNode*)malloc(sizeof(ArcNode)); p1->adjvex=j; //p1->info=w; p1->nextarc=G.vertices[i].firstarc; G.vertices[i].firstarc=p1;//使用了头插法 //无向图是对称的,所以还需要添加对称的边 p2=(ArcNode*)malloc(sizeof(ArcNode)); p2->adjvex=i; //p2->info=w; p2->nextarc=G.vertices[j].firstarc; G.vertices[j].firstarc=p2; } return OK; } int LocateVex(ALGraph G,VertexType v){ //在图G中查找顶点v的位置 for(int i=0;i<G.vexnum;i++){ if(G.vertices[i].data==v){ return i; } } return -1; }
-
-
邻接矩阵和邻接表
- 相同:都是图的存储结构,都能表示图中的任意两个顶点之间是否有边相连
- 不同:邻接矩阵的空间复杂度为O(n^2),邻接表的空间复杂度为O(n+e),邻接矩阵适合稠密图,邻接表适合稀疏图,邻接矩阵是唯一的,邻接表不是唯一的
- 邻接矩阵适合于稠密图,而邻接表适合于稀疏图。邻接表的空间效率高,但时间效率低。邻接矩阵的时间效率高,但空间效率低。
-
-
十字链表(有向图)
-
增加一个指针域,一个指向入度边,另外一个指向出度边
-
邻接多重表(无向图)
-
-
图的遍历
-
定义:从图的某一顶点出发访遍图中的其他顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历
-
深度优先遍历(DFS)
- 采用邻接矩阵表示图的深度优先搜索遍历
void DFS(AMGraph G,int v){ printf("%c ", G.vexs[v]); visited[v]=true;//单独设立一个数组作为标志,为1则代表已经访问过,true为1 for(int w=0;w<G.vexnum;w++){ if(G.arcs[v][w]&&!visited[w]){//判断v和w之间是否有边,且w未被访问过,visited[w]为0表示未访问过 DFS(G,w); } } }
时间复杂度为:O(n^2)
- 采用邻接表表示图的深度优先搜索遍历
时间复杂度为:O(n+e)void DFS(ALGraph G,int v){ printf("%c ", G.vertices[v].data); visited[v]=true; ArcNode *p=G.vertices[v].firstarc; while(p){ if(!visited[p->adjvex]){ DFS(G,p->adjvex); } p=p->nextarc; } }
- 采用邻接矩阵表示图的深度优先搜索遍历
-
广度优先遍历(BFS)
- 按广度优先非递归遍历连通图G
//广度优先搜索 void BFS(ALGraph G,int v){ //打印起始顶点 printf("%c ", G.vertices[v].data); //标记起始顶点已访问 visited[v]=true; //初始化队列 IniQueue(Q); //将起始顶点入队 EnQueue(Q,v); //当队列不为空时 while(!isEmpty(Q)){ //出队 DeQueue(Q,v); //获取起始顶点的第一个邻接点 ArcNode *p=G.vertices[v].firstarc; //遍历起始顶点的所有邻接点 while(p){ //如果邻接点未被访问 if(!visited[p->adjvex]){ //打印邻接点 printf("%c ", G.vertices[p->adjvex].data); //标记邻接点已访问 visited[p->adjvex]=true; //将邻接点入队 EnQueue(Q,p->adjvex); } //获取下一个邻接点 p=p->nextarc; } } }
时间效率:如果使用邻接矩阵表示图,时间复杂度为O(n^2),如果使用邻接表表示图,时间复杂度为O(n+e)
-
-
图的应用
-
最小生成树
-
生成树概念:一个连通图的生成树是一个极小连通子图,它含有图中全部顶点,并且只有足以构成一棵树的n-1条边
-
普利姆算法(Prim)(适用于稠密图)
- 算法思想:从图中任意取一个顶点作为起始点,然后寻找与该顶点相邻的各个顶点中权值最小的边,将该边及其对应的顶点加入到生成树中,然后从新生成的树中继续寻找与该顶点相邻的各个顶点中权值最小的边,将该边及其对应的顶点加入到生成树中,重复上述步骤,直到生成树中含有n-1条边为止
- 时间复杂度:O(n^2)
-
克鲁斯卡尔算法(Kruskal)(适用于稀疏图)
- 算法思想:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止
- 时间复杂度:O(eloge)
-
-
最短路径
-
迪杰斯特拉算法(Dijkstra)(单源最短路径)
-
弗洛伊德算法(Floyd)(所有顶点到所有顶点的最短路径)
-
-
拓扑排序
-
有向无环图(DAG):有向图中不存在环的图
-
AOV网:顶点表示活动,有向边表示活动间的先后顺序
-
AOE网:顶点表示活动的开始或结束事件,有向边表示活动,弧上的权表示活动持续的时间
-
拓扑排序:有向无环图(DAG)的顶点序列,若满足:若存在一条从顶点A到顶点B的路径,那么在序列中顶点A出现在顶点B之前
- 判断有向图中是否有环,进行拓扑排序后全部结点都在拓扑序列中,则AOV网中没有环
-
-
关键路径
- 关键路径:AOE网中从源点到汇点的最长路径长度,关键路径上的活动是关键活动
- 路径长度:路径上各活动持续时间之和
注释:图中l(a1)=vl(v2)-a1=6-6=0
l(a6)=vl(v6)-a6=10-2=8
-