1.术语和游戏规则
有些问题中,顶点的连接是有方向的,这样的图成为有向图。n个顶点可能的有向图的数目要比无向图的数目大很多,所以有向图是更加复杂的。
定义1:有向图(digraph,directed graph)由顶点集和连接有序顶点对的有向边集组成,我们说从一条边的其中一个顶点指向另一个顶点。
定义2:在一个有向图中的有向路径(directed path)是顶点的一个序列,其中有一条有向边将序列中的每一个顶点与该序列中该顶点的后继连接起来。如果存在从s到t的一条路径,成为从s到t是可达的(reachable)。
有向图顶点的入度(indegree)为通向该顶点的边的数目。出度(outdegree)为从该顶点出发的有向边数。出度为0的点不能到达任何顶点,称为汇点(sink);入度为0的点称为源点(source),无法从其他顶点达到。
允许每个顶点自环且出度为1的有向图称为一个映射(map)。
有向图的逆图(reverse)也是一个有向图,有时候取逆可以化简问题。
求有向图的逆图(邻接表):
创建一个新图,将所有边反转。
Graph
定义3:有向无环图(directed acyclic graph)是没有有向环的有向图。
定义4:如果有向图中的每个顶点均由每个顶点可达,则称有向图是强连通的。
强连通的表示顶点都位于图中的某个环上。
性质1:不是强连通的有向图有一组强连通分量以及由一个连通分量到另一个连通分量的一组有向边组成。
性质2:给定一个有向图D,可以生成一个有向图K(D),其顶点是D的连通分量,边是连接连通分量的边。K(D)是一个DAG。
2.有向图中的DFS
有向图DFS树的链接对应图中不同的边。
树边:递归调用边(无环链接);
回边:返回树的祖先节点(回到起点);
下边:从一个顶点到DFS树的子孙节点(形成环);
交叉边:从一个顶点到另一个顶点,此顶点不是DFS树中的子父节点(返回已存在环起点)。
有向图的DFS:
标记每个边的性质
void
有向环检测:
一个有向图是DAG,当且仅当使用DFS检测时,没有遇到回边。
单源点可达性:
使用一个从s开始的DFS,可以解决从顶点s开始的单源点可达性问题,所用时间和与可达性顶点所导出的子图的边数成正比。
3.可达性和传递闭包
定义5:有向图的传递闭包(transitive closure)也是一个有向图,其中包括同样的顶点,但在此传递闭包中有一条从s到t的边,当且仅当在给定图中存在从s到t的有向路径。
传递闭包描述了可达性的所有必要信息。
使用布尔矩阵相乘(Boolean matrix multiplication)来解决邻接矩阵的传递闭包。
性质:利用Warshall算法,可以在与
可以在原位构建闭包:
for
证明:第一次循环填充了所有包含0顶点的s-t,s-t的可达性取决于其路径包含的顶点,从0开始依次遍历,就可以分析出所有s-t路径的可达性。
改进的Warshall算法:
先过滤零值,优化效率
void
性质:对于有向图,可在常量时间内支持可达性测试,使用的空间与
有向图的传递闭包和许多问题存在一种密切的联系。比如s-t最短路径,也可以按照上述方法更新A[s][t],判断条件是A[s][i]+A[i][t] < A[s][t]。
基于DFS的传递闭包(邻接表):
使用DFS来依次填充每个顶点的可达性
void
性质:对于有向图的抽象传递闭包,使用DFS可以支持常量时间的查询,所需空间和
4.等价关系和偏序
将抽象闭包的概念用集合来解释。对于一个数对(s, t),等价关系(equivalence relation)是s=t,得到t=s。比如模运算,s和t同余。图中的连通性,位于同一连通分量中的点可以交换。
偏序(partial order)则只有s->t或t->s中的一个成立,两者无关。偏序具有传递性,没有对称性。比如子集包含,DAG中的路径。
5.有向无环图
有向无环图(DAG)的模型是偏序,应用是调度(scheduling)。通过安排一组有序任务,这种问题成为拓扑排序。
DAG也可以看成树,可以简单的递归遍历。
定义6:一棵二叉树是一个有向无环图,每个节点均出发两条边,可能为空也可能不为空。
使用二叉DAG来表示二叉树:
自底向上构建
int
6.拓扑排序
拓扑排序的目标是能够处理DAG中的顶点,从而使每个顶点在其指向所有顶点之前得到处理。有两种方式来定义操作:
拓扑排序(重新编号)给定一个DAG,对其顶点重新编号,使得每一条有向边从编号小的顶点指向编号大的顶点。
拓扑排序(重新排列)给定一个DAG,在水平线上对其顶点重新排列,使得所有有向边都从左指向右。
逆拓扑排序是相反的过程。
逆拓扑排序算法就是标准的DFS,只要在递归最后编号即可,递归的终点就是汇点。
性质:DFS中的后序编号能得出任何DAG的一个逆拓扑排序。
如果在递归最后用栈将顶点存储,那么弹出栈就形成拓扑排序序列。
逆拓扑排序(邻接表):
static
BFS也同样可以实现上述过程,因为DAG至少有一个源点和一个汇点。
基于源点队列的拓扑排序:
首先遍历一遍图收集源点,将源点置于队列中,
将源点出队排序,由源点可以再派生出新的源点,入队。直到队列为空。
#include
7.有向无环图中的可达性
DAG的传递闭包是单向的,使用拓扑排序可以化简算法。
使用DFS树遍历每个顶点,生成该顶点的传递闭包,这与拓扑排序是等价的。要注意的是交叉边,每个交叉边都要依次查看每个顶点是否可达。
DAG的传递闭包:
dfs计算每个顶点,忽略下边,因为子树的处理在最后的循环中进行。
void
8.有向图的强连通分量
有向图中的强连通分量表示一个环。环中的顶点互相可达。
有高效的算法可以在线性时间内求解强连通分量问题。
Kosaraju算法
首先在其逆图上生成拓扑排序序列(后序数组),然后在该序列上再次运行DFS找出下一个搜索的顶点(具有最大后序编号的未被访问的顶点)。
先找到汇点(逆图的拓扑排序终止于汇点,逆序从逆图的源点开始)开始遍历,孤立的汇点终止,环中的假汇点会找到强连通分量。
static
性质:Kosaraju的方法可以在线性时间和线性空间内找出一个图的强连通分量。
然而还有天才的算法,Tarjan算法和Gabow算法。仅使用一遍DFS完成要求。
Tarjan算法
Tarjan算法类似于寻找无向图中的桥的算法,用于寻找回边。首先,以拓扑排序的顺序考虑顶点,找到汇点,利用回链,汇点将遍历完整个连通分量。
使用显式栈来存放顶点的调用,递归完成就更新环的起点,找到环之后标记组群和已读。
void
Gabow算法
Gabow算法使用一个栈path来记录路径。
void
性质:Tarjan算法和Gabow算法都可以在线性时间内找出一个有向图的强分量。