深度优先搜索
深度优先搜索是对先序遍历的推广,从某个顶点v开始处理v,然后递归遍历所有和v邻接的顶点,在图中应用深度优先遍历需要注意的问题是图可能有圈,每次访问到一个节点需要标记这个节点是已经访问过的,然后对所有没有被标记的节点递归调用深度优先搜索。深度优先遍历的通用模板如下:
void Dfs(Vertex V)
{
Visited[V] = True;
for each W adjacent to V
if (!Visited[W])
Dfs(W);
}
若图是无向不连通的,或者是有向非强连通的,那么可能访问不到某些节点,此时需要搜索一个没有被访问过的顶点,对此顶点调用dfs。此算法对每条边访问一次,时间是
深度优先搜索的应用:
1.无向图
通过遍历无向图,可以生成深度优先生成树,如图
对左边的无向图应用深度优先遍历,从A开始,每一条边都在右图中,处理
的时候,发现w是未标记的,那么用树的一条边表示,若发现w已经标记了,那么画一条虚线,称为背向边,表示这条边不是树的一部分
2.双连通性
若一个连通的无向图中任一顶点删除之后,还是连通的,那么称为双连通的。若一个图不是双连通的,那么将其删除之后导致图不连通的顶点叫做割点,例如,下图是不连通的,C和D是割点:
深度优先搜索提供一个方法找到连通图中的所有割点,深度优先生成树如下图:
方法是,深度优先遍历得到一个编号Num[V],称为先序编号,然后对树中的每个顶点,计算Low[V],这个值是从v开始,最多通过一条背向边能访问到的所有节点Num值的最小值,例如,F能访问到的最高的节点是D,low[F]=Num[D]。Low[V]选择方法是:
1.Num[v]
2.所有背向边(v, w)中的最低Num[w]
3.树的正向边(v, w)中的最低Low[w]
以上三者中的最小值
代码如下:
/* 深度优先遍历分配编号 */
void AssignNum(Vertex V)
{
Vertex W;
Num[V] = Counter++;
Visited[V] = True;
for each W adjacent to V
if (!Visited[W])
{
Parent[W] = V;
AssignNum(W);
}
}
/* 分配Low值 */
void AssignLow(Vertex V)
{
Vertex W;
/* rule1,直接选Num[W],后面再比较 */
Low[V] = Num[W];
for each W adjacent to V
{
if (Num[W] > Num[V])
{
AssignLow(W);
if (Low[W] >= Num[V])
printf("%v is a articulation point", V);
/* rule3,正向边的最小值 */
Low[V] = Min(Low[V], Low[W]);
}
else if (Parent[V] != W)
/* rule2,背向边的最小值 */
Low[V] = Min(Low[V], Num[W]);
}
}
/* 整合先序和后序两个流程 */
void FindArt(Vertex V)
{
Vertex W;
Visited[V] = True;
Low[V] = Num[W] = Counter++;
for each W adjacent to V
{
if (!Visited[W])
{
Parent[W] = V;
FindArt(W);
if (Low[W] >= Num[V])
printf("%v is an articulation point\n", V);
Low[V] = Min(Low[V], Low[W]);
}
else if (Parent[V] != W)
Low[W] = Min(Low[V], Num[W]);
}
}