同树的遍历类似,对于给定的图, 沿着一些边(或弧)访问图中所有的顶点,且使每个顶点仅被访问一次,这个过程叫作图的遍历。
由于图中可能存在回路,且图的任一顶点都可能与其他顶点相邻接,所以在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。因此,在图的遍历过程中, 为了避免重复访问,可设置一个标志顶点是否被访问过的辅助数组tag[ ],其中每一个元素代表图中的一个顶点是否被访问,其初始状态是每一个元素都为0,表示图中所有顶点都没有被访问。在图的遍历过程中,一旦某一个顶点v,被访问,就立即置tag[vi]为1,表示该顶点已被访问,从而防止它被多次访问。
图的遍历通常有两种方法:深度优先遍历(depth first traversal)和广度优先遍历(breadth first traversal).这两种方法对无向图和有向图都是适用的,但在下面的讨论中将主要介绍对无向图的遍历;对于有向图的遍历,需要略微修改。
7.3.1 图的深度优先遍历(Depth First Traversal)
- 图的深度优先遍历基于深度优先搜索(depth first search,DFS)。深度优先搜索是从图中某一顶点v出发,在访问顶点v后,再依次从v的任一还没有被访问的邻接顶点w出发进行深度优先搜索,直到图中所有与顶点v有路径相通的顶点都被访问过为止。这是一个递归定义,所以图的深度优先搜索可以用递归算法实现。
- 图7-15(a)给出了深度优先搜索的示例。由于该图是连通的,所以从顶点A出发,通过一次深度优先搜索即可访问图中的所有顶点。图的深度优先遍历的访问顺序与树的前序遍历顺序类似。
图7-15(b)给出了在深度优先遍历的过程中,访问的所有顶点和经过的边,图中各顶点旁附加的数字表示各顶点被访问的次序。在图7-15(b)中,共有n-l条边连接了所有n个顶点,在此把它称为图7-15(a)的深度优先搜索生成树。 - 从指定的结点v开始进行深度优先搜索的算法思想:
1)访问结点v,并标记v已被访问。
2)取顶点v的第一个邻接顶点w。
3)若顶点w不存在,算法结束;否则继续步骤(4)。
4)若顶点w未被访问,则访问结点w,并标记w已被访问。
5)使w为顶点v的在原来w之后的下一个邻接顶点,转到步骤(3)。 - 深度优先遍历 DFT 代码实现
利用无向图邻接矩阵。
///深度优先搜索 BFS
template <class ElemType>
void DFS(const AdjMatrixUndirGraph<ElemType> &g, int v, void (*Visit)(const ElemType &))
// 初始条件:存在图g
// 操作结果:从顶点v出发进行深度优先搜索
{
ElemType e;
g.SetTag(v, VISITED); // 设置顶点v已访问标志
g.GetElem(v, e); // 取顶点v的数据元素值
Visit(e); // 访问顶点v
for (int w = g.FirstAdjVex(v); w != -1; w = g.NextAdjVex(v, w))
if (g.GetTag(w) == UNVISITED)
DFS(g, w , Visit); // 从v的尚未访问过的邻接顶点w开始进行深度优先搜索
}
///深度优先遍历DFT
#include <stack>
template <class ElemType>
void DFSTraverse(const AdjMatrixUndirGraph<ElemType> &g, void (*Visit)(const ElemType &))
// 初始条件:存在图g
// 操作结果:对无向图g进行深度优先遍历
{
int v;
for (v = 0; v < g.GetVexNum(); v++)
g.SetTag(v, UNVISITED);// 对每个顶点设置未访问标志
for (v = 0; v < g.GetVexNum(); v++)
if (g.GetTag(v) == UNVISITED)
DFS(g, v , Visit);// 从尚未访问的顶点v开始进行深度优先搜索
}
///深度优先遍历非递归算法,利用栈
template <class ElemType>
void DFSTraverse_nonrecursion(const AdjMatrixUndirGraph<ElemType> &g, void (*Visit)(const ElemType &))
{
int v,w;
for (v = 0; v < g.GetVexNum(); v++)
g.SetTag(v, UNVISITED);// 对每个顶点设置未访问标志
ElemType e;
stack<int> s;
for (v = 0; v < g.GetVexNum(); v++)
{
while(g.GetTag(v) == UNVISITED)
{
if(g.GetTag(v) == UNVISITED)
{
g.SetTag(v, VISITED); // 设置顶点v已访问标志
g.GetElem(v, e); // 取顶点v的数据元素值
Visit(e); // 访问顶点v
s.push(v);
for (w = g.FirstAdjVex(v); w != -1; w = g.NextAdjVex(v, w))
{
if(g.GetTag(w) == UNVISITED)
{
v=w;
break;
}
}
if(w!=-1)
continue;
}
while(!s.empty())
{
v=s.top();
for (w = g.FirstAdjVex(v); w != -1; w = g.NextAdjVex(v, w))
{
if(g.GetTag(w) == UNVISITED)
{
v=w;
break;
}
}
if(w==-1)
{
s.pop();
}
else
break;
}
}
}
}
- 算法效率:
(1)对于邻接矩阵表示,要在矩阵中扫描n个顶点,要执行一个双层循环,故时间复杂性为O(n^2)。
(2)设图的边数为e,对于邻接表表示,要扫描有n个分量的数组和单链表表示的e条边,故时间复杂性为 O(n+e)。