算法复习——图算法篇之拓扑排序
以下内容主要参考中国大学MOOC《算法设计与分析》,墙裂推荐希望入门算法的童鞋学习!
1. 问题背景
举一个生活中的实例,穿衣服是有顺序,比如肯定是先穿袜子,再穿鞋,不可能是先穿鞋,再穿袜子。
如上图所示,对于这类问题,我们可以用有向无环图里来表示事件发生的先后顺序,其中,有向规定了先后顺序,无环则避免了相互依赖。那么,问题就是比如在穿衣的这个生活场景中,如何确定一个可行的穿衣顺序呢?
我们可以给出以下的方案:
它要求有向图任意边的起点排在终点前面。
2. 问题定义
拓扑排序(Topological Sort)
输入:
- 有向无环图 G = < V , E > G=<V, E> G=<V,E>
输出:
- 图顶点 V V V的拓扑序 S S S,满足:对任意有向边 ( u , v ) (u, v) (u,v),排序后 u u u在 v v v之前
显然,拓扑序是不唯一的,满足条件即可。
3. 广度优先策略
3.1 算法思想
- 有向图顶点的度分为入度和出度
- 顶点 u u u的入度:终点为 u u u的边数
- 顶点 v v v的出度:起点为 u u u的边数
- 若顶点入度为0,所对应事件无制约,可直接完成
- 完成入度为0对应的事件
- 删除完成事件,产生新的入度为0点,继续完成
3.2 伪代码
输入:图G
输出:顶点拓扑序
初始化空队列q
for v ∈ V do
if v.in_degree = 0 then
Q.Enqueue(v)
end
end
while not Q.is_empty() do
u ← Q.Dequeue()
print u
for v ∈ G.Adj(u) do
v.in_degree ← v.in_degree - 1
if v.in_degree = 0 then
Q.Enqueue(v)
end
end
end
该算法的时间复杂度是 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)。
4. 深度优先策略
4.1 算法思想
-
从DFS的视角观察
-
穿衣顺序和搜索深度有关:深度越深,顺序越靠后
-
深度越深
- 发现时刻越晚
- 完成时刻越早
-
那么,我们是按照发现时刻顺序还是按照完成时刻逆序来生成拓扑序呢?
-
我们可以看下方的实例。假设从袜子出发,下一个点是鞋子,然后就走完了一棵深度优先树。袜子的发现时刻和完成时刻是1和4,鞋的发现时刻和完成时刻是2和3。接着,如果下一个访问的是短裤,则短裤的发现时刻是5,这时,就出问题了。若按发现时刻顺序执行,会先穿鞋后穿短裤,拓扑序错误。
- 继续走完该实例,会发现按照完成时刻逆序执行,没有出问题,那我们需要证明算法的正确性。
-
4.2 正确性证明
- 已知深度优先搜索确定的顺序:顶点完成时刻的逆序
- 已知拓扑序:对任意边 ( u , v ) (u, v) (u,v), u u u在 v v v前面
- 需证明对任意边
(
u
,
v
)
(u, v)
(u,v),完成时刻满足:
f
(
u
)
>
f
(
v
)
f(u)>f(v)
f(u)>f(v)
- 证明:设当前顶点为
u
u
u,搜索顶点
v
v
v
- 若 v v v为白色, v v v是 u u u的后代, f ( u ) > f ( v ) f(u)>f(v) f(u)>f(v)(括号化定理)
- 若 v v v为黑色, v v v已经完成, u u u尚未完成, f ( u ) > f ( v ) f(u)>f(v) f(u)>f(v)
- v v v不可能是灰色,因为给定的图是有向无环图,不存在后向边,若当前顶点为 u u u,但搜索到了灰色的 v v v,那只能说明 v v v发现得比 u u u发现得还早,也就存在 v v v到 u u u的反向边,成环了。
- 证明:设当前顶点为
u
u
u,搜索顶点
v
v
v
4.3 伪代码
Topological-Sort-DFS(G)
输入:图G
输出:顶点拓扑序
L ← DFS(G)
return L.reverse()
DFS(G)
输入:图G
新建数组color[1..V],L[1..V]
for v ∈ V do
color[v] ← WHITE
end
for v ∈ V do
if color[v] = WHITE then
L' ← DFS-Visit(G, v)
向L结尾追加L'
end
end
return L
DFS-Visit(G, v)
输入:图G,顶点v
输出:按完成时刻从早到晚排列的顶点L
color[v] ← GRAY
for w ∈ G.Adj[v] do
if color[w] = WHITE then
L ← DFS-Visit(G, w)
end
end
color[v] ← BLACK
向L结尾追加顶点v
return L
该算法的时间复杂度是 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)。