有向图 G = ( V , E ) G=(V,E) G=(V,E) 的拓扑排序
拓扑排序概念
拓扑排序是针对有向图而言的,无向图中不存在拓扑排序的概念。所谓拓扑排序,即为有向图中全部顶点的一种线性排序。这个线性排序结果需要满足一定的条件,即对于有向图中的任意两个顶点 u u u和 v v v,若图中存在有向边 u → v u \rightarrow v u→v,则在拓扑排序结果中顶点 u u u一定位于 v v v的前面。
那么,由拓扑排序的概念可以推出,只有有向图中入度为0的顶点才能成为拓扑排序的第一个顶点。这个原则也是解决有向图拓扑排序的一个指导性原则。
A O V AOV AOV网络
由于存在一些概念上的联系,这里简单插入介绍一下
A
O
V
AOV
AOV网络的概念。
Activity On Vertex Network,顶点表示活动,边表示活动间的先后关系的网络图。拓扑排序是对
A
O
V
AOV
AOV网络的一种简化或者调度活动的执行顺序的过程。
由于网络中,不同的活动之间可能存在相互依赖,因此拓扑排序最终给出了一种合理地调度活动执行的先后顺序。如果所有活动之间存在着一种或多种合理的顺序,能够顺利完成全部活动,则表明该 A O V AOV AOV网络为有向无回路图或有向无环图。若某个活动A依赖活动B,而活动B又依赖活动A,则表明 A O V AOV AOV网络存在回路或向环。因此无法合理的完成全部活动。
拓扑排序方法
那么,如何确定一个有向图是否具有拓扑排序,也即是确定一个有向图是否存在回路或环呢?这里给出执行拓扑排序的方法:
不断重复地寻找入度为0的顶点,执行如下操作:首先将该顶点作为拓扑排序的输出结果,然后将该顶点及顶点关联的出边从图中删去。循环结束条件为:所有的顶点都已搜索完毕,或着顶点未搜索完,但是顶点的入度不为0,无法删除。
解释一下
入度(inDegree):顶点的入度表示有向图中以该顶点为终点的边的个数;出度(outDegree):顶点的出度表示有向图中以该顶点为起点的边的个数。因此,所谓的顶点的出边是指以顶点为起点的边。
由拓扑排序推出的原则可知,拓扑排序的第一个顶点即为入度为0的点,那么在有向图中删除该顶点后的构成的新图,重复地进行拓扑排序即可得到最终的拓扑排序结果。
具体到解决问题的方法上,我们直到关于图的搜索一般有两种方式:广度优先搜索和深度优先搜索。
广度优先搜索步骤
- 定义inDegree用于存储搜索过程中每一个顶点的入度。
- 遍历一边图的邻接表,将每一个顶点的邻接表转化为对应顶点的入度。
具体地,对于一条有向边 u → v u \rightarrow v u→v,则inDegree(v) += 1。 - 选择入度为0的顶点开始搜索,将顶点作为拓扑排序输出。
- 对于已经完成搜索的顶点,更新其邻接表中顶点的入度,值减一。
- repeat 3,4,until 循环结束条件。
- 循环结束条件:a、搜索完有向图中全部顶点,b、无法完成全部顶点的搜索。
- 满足条件a时,表明有向图存在拓扑排序结果;满足条件b时,则表明该有向图存在回路或环,不存在拓扑排序。
BFS Pseudocode
e
d
g
e
s
edges
edges 邻接表集合
i
n
D
e
g
r
e
e
s
inDegrees
inDegrees 顶点的入度集合
b
f
s
Q
bfsQ
bfsQ 搜索队列,存放入度为0的顶点
t
o
p
o
L
i
s
t
topoList
topoList 存储拓扑排序结果
BFS(G(V, E))
//初始化
for u in G(V)
for v in edges(u)
do inDegrees(v) += 1
//放入入度为0的顶点
for degree in inDegrees
do if degree == 0 then
bfsQ.push(i);
//循环搜索
while bfsQ not empty do
//搜索到入度为0的顶点
vertex = bfsQ.pop
//插入到拓扑排序结果中
topoList.add(vertex)
//搜索顶点vertex的邻接表
for v in edges(vertex)
//更新顶点v的入度值
inDegrees(v) -= 1
do if inDegrees(v) == 0
//顶点入度减为0时添加待搜索队列中
then bfsQ.push(v)
//搜索结束后,判断结束条件
//当拓扑排序结果为全部的顶点时,表明满足循环结束条件a,否则满足循环结束条件b。
return topoList.size() == |V| ? topoList: 0;
深度优先搜索步骤
DFS在搜索过程中顶点具有三种状态:未搜索(0),搜索中(1),已搜索(2)。
三种状态的含义如下:
未搜索(0):表示搜索过程中该顶点尚未被发现,属于一个新顶点,
搜索中(1):表示该顶点已被发现, 但其邻接顶点还尚未被搜索完,
已搜索(2):表示该顶点及其邻接顶点都完成搜索。
DFS Pseudocode
e
d
g
e
s
edges
edges 邻接表集合
v
i
s
i
t
e
d
visited
visited 用于保存顶点的搜索状态
S
S
S 栈,保存拓扑排序结果
algorithm for DFS(G(V,E))
//初始化visited
for u in V[G]
do visited(u) = 0
for u in V[G]
do if visited(u) == 0 then
//深度优先搜索全部的顶点
DFS(u)
拓扑排序结果为S出栈的顺序,原因是先完成搜索的顶点存放到栈的底部
DFS(u)
//顶点的状态设为搜索中
do visited(u) = 1
for v in edges(u)
//深度优先的方式进行邻接表的搜索
do if visited(v) == 0 then
//遇到未搜索状态的顶点,继续进行深度优先搜索
DFS(u)
do if visited(v) == 1 then
//遇到搜索中状态的顶点,说明此前该顶点已经在这条深度优先搜索路径中出现两次
//这里表明有向图中存在回路或环
do if visited(v) == 2 then
//pass 不重要的
trivial
//到这里说明顶点u已经完成深度优先搜索,更新其搜索状态为已搜索
visited(u) == 2
//入栈
S.push(u)