图的遍历是图的重要操作,对图和网的许多操作都是建立在对图的遍历操作的基础之上,如图的连通性问题、拓扑排序问题等。通常有两种遍历图的方法,即深度优先搜索遍历和广度优先搜索遍历,这两种遍历方法对无向图和有向图都适用。
1.深度优先搜索遍历
图的深度优先搜索遍历类似于树的先根次序遍历,是树的先根次序次序遍历的推广,其递归定义如下:
(1).从图中的某个顶点Vo出发并访问它;
(2).依次从Vo的未被访问的邻接顶点出发深度优先遍历图,直到图中所有从Vo出发有路径可达的顶点都已被访问到;
(3).若图中还有顶点未被访问到,则另选一个未被访问的顶点记作Vo转(1),否则图的深度优先遍历结束。
在深度优先搜索遍历开始时,图G的初始状态是所有顶点都未曾被访问;首先从某一顶点Vo出发并访问它,然后访问与Vo相邻接的顶点Vi,下一个要访问的是与顶点Vi相邻接的尚未被访问的点Vj,再下一个是与Vj相邻接的尚未被访问的顶点Vk,······,以此类推直到某个顶点所有的邻接顶点都已被访问过是达到最“深处”;此时逐层回退到某个尚有邻接顶点未被访问过的顶点Vr,再从Vr的一个未被访问的邻接顶点出发重复上述过程,直到图中从Vo出发有路径可达的顶点都被访问到时,图的一个连通分量深度优先搜索遍历结束,对于非连通图中的其他连通分量,还要继续上述的深度优先搜索遍历过程,直到图中的所有顶点都被访问时为止。
假设图用邻接表做存储结构,图中n个顶点的表头结点存入数组g[n+1]中,并用1~n作为标识每一个顶点序号,g[i]存放顶点Vi的有关信息,V作为给定的出发顶点序号。为了在遍历过程中区分顶点是否已经被访问过,设置标识数组c[n+1],初始值全为0,一旦顶点Vi被访问时置c[i]为1。从顶点V出发按深度优先搜索遍历图的递归算法dfs如下:
void dfs(adjlist g[],,int v,int c[])//从顶点v出发对图g深度优先搜索遍历的递归算法
{
int i;
nodetype *p;//P为指向表结点的指针变量
c[v]=1;
printf("%d\n",v);//访问顶点v
for(p=g[v].next;p!=NULL;p=p->next)//p从v的第一个邻接顶点开始
{
i=p->adjvex;//p所指结点序号送i中
if(c[i]==0)
dfs(g,i,c);//对尚未被访问过的结点递归调用dfs深度搜索
}
}
对整个图的遍历算法travergraph如下:
void travergraph(adjlist g[],int n)//对图按深度优先(或广度优先)搜索遍历
{
int v;
int c[n+1];
for(v=1;v<=n;v++)
c[v]=0;//标志数组初始化
for(v=1;v<=n;v++)
if(c[v]==0)//深度优先遍历图g
dfs(g,v,c);//广度优先遍历时调用dfs(g,v,c);
}
在遍历时,对每个顶点至多调用一次dfs函数;因为一旦某个顶点被标识为已被访问,就不会再从它出发进行搜索。因此,遍历图的过程实质上是对每个顶点查找其邻接顶点的过程,其时间耗费取决于所采用的存储结构。
广度优先搜索遍历类似于树的层次遍历,是树的层次次序遍历的推广,其定义如下:
(1).从图中的某个顶点Vo出发并访问它;
(2).依次访问Vo的各个未被访问的邻接顶点,然后分别从这些邻接顶点出发依次访问他们的邻接顶点,并使先被访问顶点的邻接顶点先于后被访问顶点的邻接顶点,直到图中已被访问过的顶点的邻接顶点都被访问到。
(3).若图中还有顶点未被访问到,则另选一个未被访问的结点记作Vo转(1),否则图的广度优先搜索遍历结束。
换句话说,图的广度优先搜索遍历过程是,从Vo出发由近及远访问有路径可达且路径长度为1,2,3,·····的各顶点。
由于先被访问顶点的邻接顶点先于后被访问顶点的邻接顶点被访问,在广度优先搜索遍历时应设置队列q存放已被访问过的顶点以保证按层次一次访问他们的邻接顶点;同时需要设置一个数组c记录顶点是否已被访问过的情况。假定图以邻接表存储,其他约定也和深度优先搜索算法相同,图的广度优先搜索bfs可描述如下:
void bfs(adjlist g[],int v,int c[])//从顶点v对图g按广度优先搜索遍历
{
int q[N+1],r=0,f=0;//q数组为队列,f,r分别为队头队尾指示器变量
nodetype *p;//p为指向表结点的指针变量
c[v]=1;//访问结点v,即从v开始对图开始遍历
printf("%d\n",v);
q[0]=v;//刚访问过的顶点v进队列
while(f<=r)//当队列q不空时
{
v=q[f++];//从队列q的队头取一个结点,同时该顶点出队列
p=q[v].next;//p指向v的第一个邻接顶点
while(p!=NULL)
{
v=p->adjvex;//p所指顶点序号送v中
if(c[v]==0)
{
c[v]=1;//访问顶点v并置已被访问标志
printf("%d\n",v);
q[++r]=v;//访问过的顶点进队列
}
p=p->next;//下一个邻接顶点送p中
}
}
}
在上述算法中,每一个顶点至多进一次队列。图的遍历过程实质上是通过边或弧找邻接顶点的过程,因此广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同都为O(n+e),两者的区别仅在于对顶点的访问次序不同