图算法是个庞大的家族,其中大部分成员的主体框架都可以归结于图的遍历。图的遍历需要访问所有顶点一次且仅 一次,此外,图遍历同时还要访问所有的边一次且仅一次。经过一次遍历,树边的顶点共同构成了原图的一颗遍历树。
为防止对顶点的重复访问,在遍历的过程中,需要动态地设置各顶点的不同状态,并且随着遍历的进程不断地转换状态,直至最后的“访问完毕”,图的遍历更加强调对处于特定状态顶顶啊的甄别和查找,故也称作图搜索。
最基本而典型的图搜索算法包括:广度优先搜索(BFS),深度优先搜索(DFS),优先级搜索等(PFS),本文主要介绍图的深度优先搜索(depth-first search,DFS),本文使用的图数据结构参见之前博客https://blog.csdn.net/qq_18108083/article/details/84870399
策略:优先选取最后一个被访问到的顶点的邻居。始自图中顶点s的BFS搜索,将首先访问顶点s,再从s所有尚未访问到的邻居中任取一个顶点,并以此为基点,递归地执行DFS搜索。这个过程具有先入先后的特点,故采用栈结构作为辅助容器或直接使用递归方法,这里使用递归进行实现。
实现:由于整个图可能具有多个连通域,从单个顶点开始的DFS可能不能遍历到图中的所有顶点,所以DFS函数能够遍历从顶点s开始的单个连通域,而dfs函数则对所有顶点进行检查,只要未曾被访问过,就从该点开始一次新的DFS搜索,这样就能保证所有的连通域都能够被遍历到。
template<typename Tv, typename Te> void graph<Tv, Te>::DFS(int v, int& clock)
{
status(v) = DISCOVERED; //标记当前节点为发现
dTime(v) = ++clock;
for (int u = firstNbr(v); u > -1; u = nextNbr(v, u)) //遍历所有邻居顶点
{
switch (status(u))
{
case UNDISCOVERED: //尚未发现的顶点,继续深入遍历
status(u) = DISCOVERED; //标记为已发现
type(v, u) = TREE;
parent(u) = v;
DFS(u, clock);
break;
case DISCOVERED: //已被发现但是尚未遍历完成的顶点,那就是祖先啊
type(v, u) = BACKWARD;
break;
default: //VISITED 已经遍历完成,根据dTime判断是FORWARD还是CROSS
type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS;
break;
}
}
status(v) = VISITED;
fTime(v) = ++clock;
}
template<typename Tv, typename Te> void graph<Tv, Te>::dfs(int s)
{
reset(); //复位所有顶点和已存在边的状态为未被发现,未确定
int clock = 0; //时间标签
int v = s;
do
{
if (status(v) == UNDISCOVERED)
DFS(v, clock); //对每个顶点都进行一次单连通域深度优先搜索
cout << "v----" << v << endl;
} while ((v=(++v%n)) != s);
}
效率:若图G=(V,E)中共有n个顶点和e条边,则DFS仅需O(n+e)时间。