图的两种遍历方式

图的遍历

     从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历。根据遍历路径的不同,通常有两种遍历图的方法:深度优先遍历广度优先遍历。它们对无向图和有向图都适用。图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。
图的遍历算法设计需要考虑三个问题:

(1)的特点是没有首尾之分,所以算法的参数要指定访问的第一个顶点。
(2)对图的遍历路径有可能构成一个回路,从而造成死循环,所以算法设计要考虑遍历路径可能出现的死循环问题。
(3)一个顶点可能和若干个顶点都是邻接顶点,要使一个顶点的所有邻接顶点按照某种次序被访问。

7.3.1 深度优先搜索遍历

     深度优先遍历的思想类似于树的先序遍历。其遍历过程可以描述为:从图中某个顶点v出发,访问该顶点,然后依次从v的未被访问的邻接点出发继续深度优先遍历图中的其余顶点,直至图中所有与v有路径相通的顶点都被访问完为止。

     假设给定图G的初始状态是所有顶点均未曾访问过,在G中任选一顶点vi 为初始出发点,则深度优先遍历可定义如下:首先访问出发点,并将其标记为已访问过,然后,依次从vi 出发遍历vi 的每一个邻接点,若vj未曾访问过,则以vj 为新的出发点继续进行深度优先遍历,直至图中所有和vi有路径相通的顶点都被访问到为止。因此,若G是连通图,则从初始出发点开始的遍历过程结束,也就意味着完成了对图G的遍历。
对于图7-6所示的无向连通图,若顶点v0为初始访问的顶点,则深度优先遍历顶点访问顺序是:v0→v1→v2→v5→v4→v3。

     连通图的深度优先遍历递归算法可描述为:

(1)访问顶点vi并标记顶点vi为已访问;
(2)查找顶点v的第一个邻接顶点vj;
(3)若顶点v的邻接顶点vj存在,则继续执行,否则算法结束;
(4)若顶点vj尚未被访问,则深度优先遍历递归访问顶点vj;
(5)查找顶点vi的邻接顶点vj的下一个邻接顶点,转到步骤(3)。

     当寻找顶点vi的邻接顶点vj成功时继续进行,当寻找顶点vi的邻接顶点vj失败时回溯到上一次递归调用的地方继续进行。为了在遍历过程中便于区分顶点是否被访问,需附设访问标志数组visited[ ],其初值为0,一旦某个顶点被访问,则其相应的分量置为1。
以邻接矩阵和邻接表作为图的存储结构给出深度优先遍历的递归算法如下:


DFS1:图是按照邻接矩阵的方式存储

DFS2:图是按照邻接表的方式存储

这两种数据结构的的DFS遍历的时间和空间复杂度不同
【算法7.3】

void DFS1 (MGraph MG, int i)
{ int j;
visited[i]=1;
printf("%3c",MG.vexs[i]);
for(j=1; j<=MG.vex_num; j++)
if(!visited[j]&&MG.arcs[i][j]==1)
DFS1 (MG, j);
}

void DFS2(AdjList G,int i)
{ int j;
EdgeLinklist *p;
visited[i]=1;
printf("%3c",G.vertices[i].Elem);
for(p=G.vertices[i].firstedge;p;p=p->next)
{j=p->adjvex;
if(!visited[j])
DFS2(G, j); }
}

算法分析:

遍历图的过程实质是对每个顶点搜索邻接点的过程,具有n个顶点e条边的连通图,主要时间耗费在从该顶点出发遍历它的所有邻接点上。用邻接矩阵表示图时,遍历一个顶点的所有邻接点需花费O(n)时间来检查矩阵相应行中所有的n个元素,故从n个顶点出发遍历所需的时间为O(n2),即算法的时间复杂度为O(n2)。用邻接表表示图时,遍历n个顶点的所有邻接点即是对各边表结点扫描一遍,故算法DFS2的时间复杂度为O(n+e)。算法DFS1和DFS2所用的辅助空间是标志数组和实现递归所用的栈,它们的空间复杂度为O(n)。

7.3.2 广度优先搜索

     图的广度优先遍历算法是一个分层遍历的过程,和树的层序遍历算法类同,是从图的某一顶点V0出发,访问此顶点后,依次访问V0的各个未曾访问过的邻接点;然后分别从这些邻接点出发,直至图中所有已被访问的顶点的邻接点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未被访问的顶点作起点,重复上述过程,直至图中所有顶点都被访问为止。
对于图7-6所示的无向连通图,若顶点v0为初始访问的顶点,则广度优先遍历顶点访问顺序是:v0→v1→v3→v2→v4→v5。遍历过程如图7-7所示



图7-7 广度优先搜索遍历过程

图的广度优先遍历算法需要一个队列以保持访问过的顶点的顺序,以便按顺序访问这些顶点邻接顶点。连通图的广度优先遍历算法为:

(1)访问初始顶点v并标记顶点v为已访问;
(2)顶点v入队列;
(3)当队列非空时则继续执行,否则算法结束;
(4)出队列取得队头顶点u;
(5)查找顶点u的第一个邻接顶点w;
(6)若顶点u的邻接顶点w不存在,则转到步骤(3),否则执行后序语句:

①若顶点w尚未被访问,则访问顶点w并标记顶点w为已访问;
②顶点w入队列;
③查找顶点u的邻接顶点w后的下一个邻接顶点,转到步骤(6)。
以邻接矩阵为图的存储结构的广度优先遍历的非递归算法源代码如下:

【算法7.4】

void BFS1(Mgraph G ,int k)
{ int i,j;
int visited[Vex_num];
SqQueue q;
initqueue(&q);
printf("visit vertex: V%d\n", G->vexs[k]);
visited[k]=1;
Enqueue(&q,k);
while(!QueueEmpty(&q))
{
i=Dequeue(&q);
for(j=0; j<G->n; j++)
{
printf("visit vertex :V%d \n",G->vexs[j]);
visited[j]=1;
Enqueue(&q,j);
}
}
}
以邻接表为图的存储结构的广度优先遍历的非递归算法

【算法7.5】

void BFS2(int v)
/*v是表头结点的下标*/
{ EdgeLinklist *ptr;
int v1, w;
printf(" %d \n", v); /* 输出该顶点 */
visited[v] =1; /* 标志置为1*/
enqueue(v); /*将该顶点入队尾 */
while((v1=dequeue()!=EOF)
{ /* 循环使属于同一层顶点的相邻顶点依次出队*/
ptr=list[v1].firstedge; /*取出该顶点的第一个相邻顶点地址 */
while(ptr!=NULL) /*循环依次访问各相邻顶点 */
{
w=ptr->adjtex; /* 取出该顶点的序号 */
ptr=ptr->next; /* 取出下一个相邻顶点的地址以备访问*/
if(visited [w]==0)
{
printf("%d \n", w);
visited[w]=1;
enqueue(w);
}
}
}
}

算法分析:对于具有n个顶点和e条边的连通图,因为每个顶点均入队一次,(while语句)执行次数为n。算法BFS1的内循环(for语句)执行n次,故算法BFS1的时间复杂度为O(n2);算法BFS2的内循环(for语句)执行次数取决于各顶点的边表结点个数,内循环执行的总次数是边表结点的总个数2e,故算法BFS2的时间复杂度是O(n+e)。算法BFS1和BFS2所用的辅助空间是队列和标志数组,故它们的空间复杂度为O(n))。

对于连通图,从图的任意一个顶点开始深度或广度优先遍历一定可以访问图中的所有顶点,但对于非连通图,从图的任意一个顶点开始深度或广度优先遍历并不能访问图中的所有顶点。对于非连通图,从图的任意一个顶点开始深度或广度优先遍历只能访问和初始顶点连通的所有顶点。

 

两种遍历方式哈!

posted @ 2012-06-22 18:50  springbarley  阅读(7647)  评论(0编辑  收藏  举报