广度优先搜索
算法介绍
英文为 Breadth-First Search, BFS。其宗旨是从源点开始,逐步向外扩散,如图所示。
用到三个数组:
-
c
o
l
o
r
[
u
]
color[u]
color[u]:
- WHITE \text{WHITE} WHITE:表示该点尚未搜索到
- GREY \text{GREY} GREY:表示该点正在搜索
- BLACK \text{BLACK} BLACK:表示该点搜索结束
- p r e d [ u ] pred[u] pred[u]:表示点 u u u在搜索中的前驱顶点(predecesor)
- d i s t [ u ] dist[u] dist[u]:表示点 u u u到源点的距离
算法:广度优先搜索
输入:图 G G G
输出:无
对于图中每个点 u u u,置 c o l o r [ u ] = WHITE color[u]=\text{WHITE} color[u]=WHITE,置 p r e d [ u ] = NULL pred[u]=\text{NULL} pred[u]=NULL
对于图中每个点 u u u,如果 c o l o r [ u ] = WHITE color[u]=\text{WHITE} color[u]=WHITE,调用 BFSVisit ( u ) \text{BFSVisit}(u) BFSVisit(u)
有多少个连通分支,以下算法就会被调用多少次:
算法: BFSVisit \text{BFSVisit} BFSVisit
输入:图 G G G,点 s s s
输出:无
置 c o l o r [ s ] = GREY color[s]=\text{GREY} color[s]=GREY
置 d i s t [ s ] = 0 dist[s]=0 dist[s]=0
初始化队列 Q Q Q 为空队列,并将 s s s 加入队列 Q Q Q
当 Q Q Q 不为空时:队首元素 u u u出列
对 u u u的所有邻接顶点 v ∈ a d j [ u ] v\in adj[u] v∈adj[u]:若 c o l o r [ v ] = WHITE color[v]=\text{WHITE} color[v]=WHITE:
置 c o l o r [ v ] = GREY color[v]=\text{GREY} color[v]=GREY
置 d [ v ] = d [ u ] + 1 d[v]=d[u]+1 d[v]=d[u]+1
置 p r e d [ v ] = u pred[v]=u pred[v]=u
v v v 加入 Q Q Q 队尾置 c o l o r [ u ] = BLACK color[u]=\text{BLACK} color[u]=BLACK
时间复杂度分析
- 对于每个顶点 u u u,我们需要查看其颜色,并对其所有邻接顶点 v v v进行操作,因此复杂度为 O ( 1 + d e g r e e ( u ) ) O(1+degree(u)) O(1+degree(u))
- 总复杂度为 ∑ u ∈ V O ( 1 + d e g r e e ( u ) ) = O ( V + 2 E ) = O ( V + E ) \sum\limits_{u\in V}O(1+degree(u))=O(V+2E)=O(V+E) u∈V∑O(1+degree(u))=O(V+2E)=O(V+E)(用到握手定理)
应用
- 无权图的单源最短路径
- 由BFS得到的 d i s t [ u ] dist[u] dist[u]是源点 s s s到顶点 u u u的单源最短距离
- 只能用于无权图,对于带权图需要使用Dijkstra算法或者Floyd算法
- 寻找连通分支数量
深度优先搜索
算法介绍
英文为 Depth-First Search, DFS。算法思想是一条道走到底,然后再回头走其他分支,其实质是一种贪心策略。
用到四个数组:
-
c
o
l
o
r
[
u
]
color[u]
color[u]:
- WHITE \text{WHITE} WHITE:表示该点尚未搜索到
- GREY \text{GREY} GREY:表示该点正在搜索
- BLACK \text{BLACK} BLACK:表示该点搜索结束
- p r e d [ u ] pred[u] pred[u]:表示点 u u u在搜索中的前驱顶点(predecesor)。
- d [ u ] d[u] d[u]:表示点 u u u被发现的时间。它和 f [ u ] f[u] f[u]都是为了便于后续分析而打上的时间戳。
- f [ u ] f[u] f[u]:表示点 u u u完成搜索的时间。
算法:深度优先搜索
输入:图 G G G
输出:无
对于图中每个点 u u u,置 c o l o r [ u ] = WHITE color[u]=\text{WHITE} color[u]=WHITE,置 p r e d [ u ] = NULL pred[u]=\text{NULL} pred[u]=NULL
置全局变量 t i m e = 0 time = 0 time=0
对于图中每个点 u u u,如果 c o l o r [ u ] = WHITE color[u]=\text{WHITE} color[u]=WHITE,调用 DFSVisit ( u ) \text{DFSVisit}(u) DFSVisit(u)
有多少个连通分支,以下算法就会被调用多少次:
算法: DFSVisit \text{DFSVisit} DFSVisit
输入:图 G G G,点 u u u
输出:无
置 c o l o r [ u ] = GREY color[u]=\text{GREY} color[u]=GREY
令 t i m e = t i m e + 1 time = time + 1 time=time+1
置 d [ u ] = t i m e d[u]=time d[u]=time
对 u u u的所有邻接顶点 v ∈ a d j [ u ] v\in adj[u] v∈adj[u]:若 c o l o r [ v ] = WHITE color[v]=\text{WHITE} color[v]=WHITE:
置 p r e d [ v ] = u pred[v]=u pred[v]=u
调用 DFSVisit ( v ) \text{DFSVisit}(v) DFSVisit(v)置 c o l o r [ u ] = BLACK color[u]=\text{BLACK} color[u]=BLACK
令 t i m e = t i m e + 1 time = time + 1 time=time+1
置 f [ u ] = t i m e f[u]=time f[u]=time
时间复杂度分析
与BFS相同,均需要对每个顶点和其邻接顶点进行遍历,因此也为 O ( V + E ) O(V+E) O(V+E)。
算法的性质
深度优先树
以算法中得到的 p r e d [ u ] pred[u] pred[u]数组,可以构建一棵以前驱顶点为父节点的树,称为深度优先树。深度优先树是原图 G G G的一个子图。
- 树边(tree edge):深度优先树中的边。
- 后向边(back edge):它的两顶点在深度优先树中是祖先和后代节点的关系,但不是父子节点关系。
定理:无向图中的边,不是树边就是后向边。
即:无向图中的边的两个端点一定是DFS搜索中的祖先和后代,不存在两条边的两个端点之间没有任何直接或间接前驱/后继关系。
证明:这个定理的正确性是显然的。令 ( u , v ) (u,v) (u,v)为 G G G中的任意一条边,不妨设 d [ u ] < d [ v ] d[u]<d[v] d[u]<d[v],即 u u u被发现的时候, v v v还没被发现。那么,根据DFS算法, v v v一定在以 u u u为根节点的子树上,故二者必有祖先和后代关系。
括号化定理
括号化定理揭示了 d [ u ] d[u] d[u]和 f [ u ] f[u] f[u]时间戳的结构。如图所示,我们在DFS算法中的两个时间戳数组可以绘成如下的甘特图:
它具有以下的性质:
- 每个时间点都是一个时间戳区间的端点。
- u u u节点是 v v v节点的后代(descendent)当且仅当 v v v的区间包含 u u u的区间; u u u节点是 v v v节点的祖先(ancestor)当且仅当 v v v的区间是 u u u的区间的子区间(subinterval)。
- u u u节点和 v v v节点是不连接的(unrelated),当且仅当 u u u和 v v v的区间没有交集(disjoint)。