6.1-1 图的基本概念
图的定义
有向图、无向图
没有箭头的那一段是弧尾,有箭头的那一段叫弧头。AE中A是弧尾,E是弧头。
简单图、多重图
顶点的度、入度、出度
顶点-顶点的关系描述
强连通就是正反都有路径,A和B是强联通的,A和E就不是强连通的
连通图、强连通图
子图
无向图
有向图(和上面无向图的定义相同)
连通分量(描述无向图的)
极大连通子图是子图,且连通,且包含尽可能多的顶点和边
强连通分量(描述有向图的)
有向图中的 极大强连通子图 称为有向图的 强连通分量(极大强连通子图是强连通分量)
生成树
生成森林
边的权、带权图/网
北京到上海的带权路径长度是200+600=800
几种特殊形态的图
n个顶点的连通图最少有n-1条边,如果在多一条边,一定有回路。
6.2-1 邻接矩阵法(数组实现的顺序存储,空间复杂度高,不适合存储稀疏图)
0表示不连接,1表示连接。
Vex数组中存储A,B,C,D,E,F,
然后在Edge中对应数组下标0,1,2,3,4,5,
这样我们表示AB这条边 就可以用Edge[0][1]=1来表示了。
因为Edge数组中只存0、1,且bool类型只占1个字节,比int类型占4个字节 占用的空间少,所以可以考虑使用bool类型表示边。
邻接矩阵存储 不带权 的图
下图中的(vi,vj)中的v是上图中的char型名为Vex的数组。
邻接矩阵存储 带权 图
邻接矩阵法的性能分析
邻接矩阵法的性质
矩阵相乘
从顶点1到顶点4的长度为2的路径的数目为1
6.2-2 邻接表法(顺序+链式存储)
无向图中:边会被链表使用两次,所以边结点的数量是2|E|,整体空间复杂度是O(|V|+2|E|).
无向图中度就是遍历对应结点的链表就行了。
有向图中:求出度容易,直接遍历对应结点的链表就行了,找 出边也容易,如果求入度、度、入边,那么需要遍历整个表才行。
6.2-3 十字链表、邻接多重表
十字链表(存储有向图)
十字链表法解决了有向图 找入边 不方便问题。
橙色连接了弧头相同的弧,绿色连接了弧尾相同的弧。
邻接多重表(存储无向图)
邻接矩阵、邻接表 存储无向图的缺点
邻接多重表 存储无向图
两个删除操作
删除一条边
删除一个顶点(同时也把与该顶点连接的边进行删除)
邻接矩阵、邻接表、十字链表、邻接多重表 总结
6.2-4 图的基本操作
Adjacent()
Neighbors()
InsertVertex()
DeleteVertex()
下图O(|E|)是 要遍历每一条边 来删除与 被删除结点 有关的边,如图中就是遍历每条边来删除与结点C有关的边。
AddEdge()
RemoveEdge()
FirstNeighbor()
NextNeighbor()
获取或设置权值
获取或设置权值 即 找到对应的边,然后获取或设置权值(获取和设置权值的时间复杂度是O(1) )。所以获取或设置权值的整体操作(包括找边和设置权值)和 只找边的时间复杂度相同。
6.3-1 图的广度优先遍历
广度优先遍历代码引入
算法存在的问题
广度优先算法(Breadth-First-Search,BFS算法)最终版
算法分析
空间复杂度
最坏情况,要一次把所有邻接点(即其余结点装入辅助队列),此时空间复杂度最高。
时间复杂度
算法时间开销主要是:访问各个顶点和查找每个顶点的邻接点。
广度优先生成树
标红,当某个顶点第一次被访问时是从哪条边过去的
广度优先生成森林
把广度优先生成树放在一起,就是广度优先生成森林。
6.3-2 图的深度优先遍历
图的深度优先遍历类似于树的先根遍历
图的深度优先遍历代码 引入
算法存在的问题(和广度优先存在的问题一样,无法处理非连通图,解决办法也一样)
深度优先遍历算法(Depth-First-Search)最终版
算法分析
空间复杂度
时间复杂度
深度优先遍历序列
深度优先生成树
标红,当某个顶点第一次被访问时是从哪条边过去的,把标红的边 和 结点单独拿出来,未标红的边去掉,就形成了深度优先生成树。
深度优先生成森林
图的遍历和图的连通性
下图,如果7仅仅能和邻接顶点6、3、8有路径而和其余顶点5、1、2、4没有路径,那么调用次数也是要多于1次的。
6.4-1 最小生成树
生成树是什么
最小生成树
带权、连通、无向
Prim算法
从p城出发
从农场出发
Kruskal算法
直到所有结点都连通,算法结束。
Prim算法和Kruskal算法对比
6.4-2 最短路径问题_BFS算法
最短路径问题介绍
BFS(广度优先求最短路径)
BFS算法求单源最短路径只适用于无权图,或所有边的权值都相同的图。
引入
BFS求无权图的单源最短路径的代码实现
按照最短路径的求解方法生成的树,高度一定是最小的。
6.4-3 最短路径问题_Dijkstra算法(迪杰斯特拉算法)
Dijkstra算法过程
初始化
第1轮
第2轮
第3轮
第4轮
如何使用数组信息?
算法的时间复杂度
每轮处理一个结点,( 花费时间O(n) )
每处理一个结点要遍历一遍dist数组查找最小的。( 花费时间O(n) )
时间复杂度 O(n²)
用于负权值带权图
首先选择还没确定最短路径的,且dist中最小的值 的 对应顶点设为true。
然后把V1设为true,算法结束。
通过算法得出的V1到V2的最短路径是7,而实际最短路径是5,说明Dijkstra算法不适用于有负权值的带权图。
6.4-4 最短路径问题_Floyd算法(弗洛伊德算法)
3个结点的弗洛伊德算法 演示
允许V0中转后,仅会更新以下一处数据。
允许V0、V1中转后,仅会更新以下一处数据。
允许V0、V1、V2中转后,仅会更新以下一处数据。
大于3个结点的弗洛伊德算法演示
不允许中转点
允许中转点V0
允许中转点V0、V1
允许中转点V0、V1、V2
允许中转点V0、V1、V2、V3
允许中转点V0、V1、V2、V3、V4
使用数组信息(A、path)找出最短路径
Floyd算法可以用于负权图
弗洛伊德算法不能解决的问题
比如从V0走到V1是-9,
如果V0->V1->V2->V0->V1是-9+(3+4±9)=-9±2=-11
这样每走一圈,V0到V1的最短路径就更小一次,没有尽头,无穷无尽。
最小路径总结
6.4-5 有向无环图描述表达式
将一棵树简化成有向无环图的过程
一道练习(答案是A.5)
6.4-6 拓扑排序
AOV网
拓扑排序引入
拓扑排序代码实现
代码实现过程
时间复杂度分析
逆拓扑排序
逆拓扑排序引入
逆拓扑排序的实现(DFS算法深度优先)
6.4-7 关键路径
概念引入
事件是一瞬间完成的。
求关键路径的步骤
1、求所有 事件 的最早发生时间
2、求所有 事件 的最迟发生时间
3、求所有 活动 的最早发生时间
每个活动的最早发生时间,就是这个活动的弧尾所连的这个事件的最早发生时间
4、求所有 活动 的最迟发生时间
5、求所有 活动 的时间余量d()
关键活动、关键路径的特性