《数据结构》王道 第六章 图

《数据结构》王道 第六章 图

1. 图的定义和基本术语

1.1 图的定义

在这里插入图片描述

1.2 无向图和有向图

在这里插入图片描述

1.3 简单图和多重图

在这里插入图片描述

1.4 顶点的度、入度、出度

在这里插入图片描述

1.5 顶点-顶点的关系描述

在这里插入图片描述

1.6 连通图和强连通图

在这里插入图片描述
在这里插入图片描述

1.7 子图、生成子图

在这里插入图片描述
在这里插入图片描述

1.8 连通分量(无向图)

在这里插入图片描述

1.9 强连通分量(有向图)

在这里插入图片描述

1.10 生成树、生成森林

在这里插入图片描述
在这里插入图片描述

1.11 边的权、带权图/网

在这里插入图片描述

1.12 无向完全图、有向完全图、稀疏图、稠密图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 图的存储

2.1 邻接矩阵法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1.1 邻接矩阵存储带权图(网)

在这里插入图片描述

2.1.2 邻接矩阵的性能分析

在这里插入图片描述

2.1.3 邻接矩阵的性质

在这里插入图片描述
以此类推,可以得到A2 的矩阵。
在这里插入图片描述
A3 也是同样的道理,则表示A[i][j] 由 i 到 j 路径长度为3的路径数目。
在这里插入图片描述
在这里插入图片描述

2.2 邻接表法(顺序+链式存储)

在这里插入图片描述
在这里插入图片描述

这种存储图的方法其实跟树的孩子表示法有点相似。
在这里插入图片描述
在这里插入图片描述
邻接矩阵存储无向图时,一条边会有两个顶点都记录着,所以边结点的数量是边的两倍,即2 * |E|。算各个结点的度也比较容易,直接看各个结点的指针区域没有连着几个结点。
当存储的是有向图时,边结点的数量就是边数,即|E|,出度就是对应结点指针区域后面连着的结点数量,而入度就得把整个邻接表遍历一遍才能知道。

在这里插入图片描述
在这里插入图片描述

2.3 十字链表(存储有向图)

在这里插入图片描述
在这里插入图片描述
十字链表找某个结点所有的边(出边和入边)都是很方便的。
在这里插入图片描述
不会考代码。

2.4 邻接多重表(无向图)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. 图的基本操作

在这里插入图片描述

3.1 判断图中是否存在某条边

在这里插入图片描述
有向图类似:
在这里插入图片描述

3.2 列出与某个顶点邻接的边

在这里插入图片描述
因为邻接矩阵的一行或一列有V个元素,所以以上邻接矩阵的操作的时间复杂度是O(|V|)。
在这里插入图片描述

3.3 插入顶点

在这里插入图片描述

3.4 删除顶点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.5 添加边

在这里插入图片描述

3.6 求第一个邻接点(常用到)

考试可以直接调用这个接口。
在这里插入图片描述
在这里插入图片描述

3.7 找下一个邻接点(常用到)

考试可以直接调用这个接口。
在这里插入图片描述

3.8 获取对应边的权值或设置权值

在这里插入图片描述

4. 图的遍历

4.1 广度优先遍历(BFS)

图的广度优先遍历有点类似于树的广度优先遍历(层序遍历)。
在这里插入图片描述
在这里插入图片描述

4.1.1 代码实现

在这里插入图片描述

4.1.2 广度优先遍历序列

在这里插入图片描述

4.1.3 遍历序列的可变性

在这里插入图片描述

4.1.4 算法存在的问题

在这里插入图片描述

4.1.5 BFS算法(最终版)

在这里插入图片描述

4.1.6 复杂度分析

在这里插入图片描述
在这里插入图片描述

4.1.7 广度优先生成树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.1.8 广度优先生成森林

在这里插入图片描述
在这里插入图片描述

4.2 深度优先遍历(DFS)

在这里插入图片描述

4.2.1 代码实现

在这里插入图片描述
在这里插入图片描述

4.2.2 算法实现的问题

在这里插入图片描述

4.2.3 DFS算法(最终版)

在这里插入图片描述

4.2.4 复杂度分析

在这里插入图片描述
在这里插入图片描述

4.2.5 遍历序列的可变性

在这里插入图片描述

4.2.6 深度优先生成树

在这里插入图片描述

4.2.7 图的遍历和图的连通性

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5. 最小生成树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.1 概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.2 Prim算法(普里姆)

在这里插入图片描述
从P城这个顶点开始构建生成树:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
整个过程就是上面这样子,当然按照这个算法,一个图从一个顶点去生成树也可能有多个最小生成树。在这里插入图片描述
例如,也可以是这样,最小代价是一样的,就是树不同而已。
从“农场”顶点开始构建生成树:
在这里插入图片描述
最小代价还是一样。

5.2.1 实现思想

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.3 Kruskal算法(克鲁斯卡尔)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.3.1 实现思想

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6. 最短路径

在这里插入图片描述

6.1 BFS求无权图的单源最短路径

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
举个例子,以顶点2作为起点:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.2 Dijkstra算法(迪杰斯特拉)

在这里插入图片描述
在这里插入图片描述
Dijkstra算法有向图和无向图都可以用。
在这里插入图片描述
这样我们研究有向图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.2.1 时间复杂度

在这里插入图片描述
循环遍历dist数组,找出最小值,所需时间复杂度是O(n);检查所有邻接自Vi ,如果是用邻接矩阵存储图的话,则需要扫描Vi 对应的一整行,即O(n),所以总的复杂度是O((n+n)*(n-1)),即O(n2 )。如果是用邻接表存储图的话,Vi 对应的链表最多有n-1个元素,所以总的时间复杂度是O(((n-1)+n) * (n-1)),同样是O(n2 )。

在这里插入图片描述
Prim算法实现思想跟Dijkstra算法实现的思想是很类似的。只不过Prim算法的lowCost数组是指,其他没有加入树的顶点要加入到树的代价;而Dijkstra算法的dist数组是指当前顶点到其他顶点的最短路径。而它们的时间复杂度是一样的。

6.2.2 Dijkstra算法不适用于带负值的带权图

在这里插入图片描述
如果这个图采用Dijkstra算法,v0到v2的最短路径是7。
在这里插入图片描述

6.3 Floyd算法

在这里插入图片描述
在这里插入图片描述

6.3.1 核心代码和复杂度

在这里插入图片描述
因为有三层循环,每个for循环都是n,所有时间复杂度是O(n3 ),即O(|V|3 )。最外一层循环是用来考虑是否采用Vk 作为中转点的。
而这个算法需要两个二维数组A和path,所有空间复杂度是O(2*n2 ),即O(|V|2 )。

举个例子:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.3.2 可用于负权值带权图

在这里插入图片描述

6.3.3 不能解决的问题

在这里插入图片描述
在这里插入图片描述

7. 有向无环图

7.1 描述表达式

在这里插入图片描述
树是自顶向下的,所以树可以算作一种有向无环图,就是把上面的指针指向同个结点也还是有向无环图。
在这里插入图片描述
在这里插入图片描述
相同的子树可以合并,从而减少结点数量,节省空间。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以最后形成的有向无环图是这样子的:
在这里插入图片描述
曾经真题也出现过这样的题:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.1.1 总结规律

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
再往上层已经没有可以合并的了,所以最后的有向无环图就是以上所示。

7.2 拓扑排序

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
有两个选择,我们先选择“准备厨具”:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
到这里之后,就依次执行下去,我们可以总结出一个规律:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
执行到这里时,已经没办法继续执行下去了。

7.2.1 核心代码

在这里插入图片描述
拓扑排序,我们先处理的是入度为0的顶点。
在这里插入图片描述
在这里插入图片描述

7.2.2 用DFS实现拓扑排序

对于有向无环图G中的任意结点u、v,它们之间的关系必然是一下三种之一:

  1. 假设结点u是结点v的祖先,则在调用DFS访问u的过程中,必然会在这个过程结束之前递归地对 v 调用DFS访问,即v的DFS函数结束时间先于u的DFS结束时间。从而可以考虑在DFS调用过程中设定一个时间标记,在DFS调用结束时,对各结点计时。因此,祖先的结束时间必然大于子孙的结束时间。

  2. 若u是结点v的子孙,则v为u的祖先,按上述思路,v 的结束时间大于u的结束时间。

  3. 若u和v没有关系,则u和v在拓扑序列的关系任意。

从而按结束时间从大到小,可以得到一个拓扑序列。

下面给出利用DFS求各结点结束时间的代码。至于拓扑序列,将结束时间从大到小排序即可得到(实际上和深度优先遍历算法完全相同,只不过加入了time变量)。

bool visited[MAX_VERTEX_NUM];    //访问标记数组
void DFSTraverse(Graph G){
	for(v=0;v<G.vexnum;++v){
		visited[v]=false;        //初始化访问标记数组 
	}
	time=0;
	for(v=0;v<G.vexnum;++v){
		if(!visited[v]){
			DFS(G,v);
		}
	}
} 
void DFS(Graph G,int v){
	visited[v]=true;
	visit(v);
	for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
		if(!visited[w]){         //w为v的尚未访问的邻接顶点 
			DFS(G,w); 
		}
	}
	time=time+1;
	finishTime[v]=time;
}

7.3 逆拓扑排序

在这里插入图片描述
拓扑排序是先处理入度为0的顶点,而逆拓扑排序是先处理出度为0的结点。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里也有多种选择,所以形成的逆拓扑排序序列也可以有多个。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.3.1 核心代码

//这里采用的是邻接矩阵法
bool ni-TopologicalSort(Graph G){
	InitStack(S);        //初始化栈,存储出度为0的顶点,这里是假设已经初始化好outdegree数组(记录当前顶点的出度)和print数组的 
	for(int i=0;i<G.vexnum;i++){
		if(outdegree[i]==0)
			Push(S,i);   //将所有出度为0的顶点进栈 
	} 
	int count=0;         //记录当前已经输出的顶点数
	while(!IsEmpty(S)){  //栈不空则存在出度为0的顶点 
		Pop(S,i);        //栈顶元素出栈
		print[count++]=i;//将输出顶点保存在print数组中
		for(int j=0;j<G.vexnum;j++){     //这里我采用的是邻接矩阵的存储方式 
			//将所有指向i的顶点的出度减1,并且将出度减为0的顶点压入栈S 
			if(A[j][i]==1){              //说明j->i有出边 
				if(!(--outdegree[j])){
					Push(S,j);           //出度为0,则入栈 
				}
			}
		} 
	} 
	if(count<G.vexnum){    //count小于顶点数 
		return false;      //排序失败,有向图中有回路 
	}else{
		return true;       //逆拓扑排序成功 
	} 
}

在这里插入图片描述

7.3.2 用DFS实现逆拓扑排序

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当出现回路时,可以参考这个博主的链接:
图的逆拓扑排序
在这里插入图片描述

7.4 关键路径

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.4.1 求关键路径的步骤

在这里插入图片描述

求所有路径的最早发生时间

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

求所有事件的最迟发生时间

在这里插入图片描述
在这里插入图片描述

求所有活动的最早发生时间

在这里插入图片描述

求所有活动的最迟发生时间

在这里插入图片描述

求所有事件的时间余量

在这里插入图片描述

求得关键活动和关键路径

在这里插入图片描述

7.4.2 关键活动、关键路径的特征

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值