7.3图的遍历

7.3图的遍历

图的遍历就是从图中的某个顶点出发,按某种方法对图中的所有顶点访问且 仅访问一次。
图的遍历比起树的遍历要复杂得多。由于图中顶点关系是任意的,即图中顶点之间是多对多的关系,图可能是非连通图,图中还可能有回路存在,因此在访问了 某个顶点后,可能沿着某条路径搜索后又回到该顶点上。为了保证图中的各顶点 在遍历过程中访问且仅访问一次,需要为每个顶点设一个访问标志,因此要为图设置一个访问标志数组 visited[n],用于标示图中每个顶点是否被访问过,它的初 始值为 0(假),一旦顶点 vi访问过,则置 visited[i]为 1(真),以表示该顶点已访问。

对于图的遍历,通常有两种方法,即深度优先搜索和广度优先搜索。

1、深度优先搜索

深度优先搜索(Depth_First Search,DFS)是指按照深度方向搜索,它类似 于树的先根遍历,是树的先根遍历的推广。

深度优先搜索的基本思想是:

  • (1) 从图中某个顶点 v0出发,首先访问 v0。
  • (2) 找出刚访问过的顶点的第一个未被访问的邻接点,然后访问该顶点。 以该定点为新顶点,重复此步骤,直到刚访问过的顶点没有未被访问的 邻接点为止。
  • (3) 返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点 的下一个未被访问的邻接点,访问该顶点。然后执行步骤(2)。

下图给出了一个深度优先搜索的过程图示,其中箭头代表访问方向,箭头代表回溯方向,箭头旁边的数字代表搜索顺序,A 为起始顶点。

首先访问 A,然后按图中序号对应的顺序进行深度优先搜索。图中序号对应 步骤的解释如下:

  • (1)顶点 A 的未访邻接点有 B、E、D,首先访问 A 的第一个未访邻接点 B;
  • (2)顶点 B 的未访邻接点有 C、E,首先访问 B 的第一个未访邻接点 C;
  • (3)顶点 C 的未访邻接点只有 F,访问 F;
  • (4)顶点 F 没有未访邻接点,回溯到 C;
  • (5)顶点 C 已没有未访邻接点,回溯到 B;
  • (6)顶点 B 的未访邻接点只剩下 E,访问 E;
  • (7)顶点 E 的未访邻接点只剩下 G,访问 G;
  • (8)顶点 G 的未访邻接点有 D、H,首先访问 G 的第一个未访邻接点 D;
  • (9)顶点 D 没有未访邻接点,回溯到 G;
  • (10)顶点 G 的未访邻接点只剩下 H,访问 H;
  • (11)顶点 H 的未访邻接点只有 I,访问 I;
  • (12)顶点 I 没有未访邻接点,回溯到 H;
  • (13)顶点 H 已没有未访邻接点,回溯到 G;
  • (14)顶点 G 已没有未访邻接点,回溯到 E;
  • (15)顶点 E 已没有未访邻接点,回溯到 B;
  • (16)顶点 B 已没有未访邻接点,回溯到 A。

至此,深度优先搜索过程结束,相应的访问序列为:A、B、C、F、E、G、 D、H、I。
在这里插入图片描述

算法思想
首先实现对 v0所在连通子图的深度优先搜索,用递归算法实现的基本过程为:
(1) 访问出发点 v0 。
(2) 依次以 v0的未被访问的邻接点为出发点,深度优先搜索图,直至图中 所有与 v0 有路径相通的顶点都被访问。 若是非连通图,则图中一定还有顶点未被访问,需要从图中另选一个未被访 问的顶点作为起始点,重复上述深度优先搜索过程,直至图中所有顶点均被访问 过为止。

算法描述

#define True  1 
#define False  0 
#define Error –1   /*出错*/ 
#define Ok 1 int visited[MAX_VERTEX_NUM];   /*访问标志数组*/ 
void  TraverseGraph (Graph g) /* 在图 g 中寻找未被访问的顶点作为起始点,并调用深度优先搜索过程进行遍 历。Graph 表示图的一种存储结构,如邻接矩阵或邻接表等 */ 
{ 
	for (vi=0; vi<g.vexnum; vi++)  
		visited[vi]=False ;   /*访问标志数组初始*/ 
	for( vi=0; vi<g.vexnum; vi++)   /* 循环调用深度优先遍历连通子图的操作 */ /* 若图 g 是连通图,则此调用只执行一次 */   
		if (!visited[vi] )  
			DepthFirstSearch(g,vi); 
}/* TraverseGraph */   

深度优先遍历图 g 算法

void  DepthFirstSearch(Graph g,  int v0) /* 深度遍历 v0 所在的连通子图 */ 
{ 
	visit(v0);  
	visited[v0] =True;     /*访问顶点 v0,并置访问标志数组相应分量 值*/ 
	w=FirstAdjVertex(g,v0); 
	while ( w!=-1)                /*邻接点存在.*/   
	{ 
		if(! visited [w] )   
		DepthFirstSearch(g,w);    /* 递 归 调 用 DepthFirstSearch*/    
		w=NextAdjVertex(g,v0,w);   /*找下一个邻接点*/   
	} 
}/*DepthFirstSearch*/ 

深度优先遍历 v0 所在的连通子图算法
上述算法中对于 FirstAdjVertex(g,v0)以及 NextAdjVertex(g,v0,w)并 没有具体实现。如果图的存储结构不同,对应操作的实现方法不同,时间耗费也不同。下面分别用邻接矩阵和邻接表具体实现。

(1)用邻接矩阵方式实现深度优先搜索

算法描述

void  DepthFirstSearch(AdjMatrix g,  int v0)   /* 图 g 为邻接矩阵类型 AdjMatrix */  
{ 
	visit(v0); 
	visited[v0]=True;   
	for ( vj=0;vj<n;vj++)     
		if (!visited[vj] && g.arcs[v0][vj].adj==1)         
			DepthFirstSearch(g, vj); 
}/* DepthFirstSearch */ 

采用邻接矩阵的 DepthFirstSearch 算法

(2)用邻接表方式实现深度优先搜索

算法描述

void  DepthFirstSearch(AdjList g,  int v0)    /*图 g 为邻接表类型 AdjList */ 
{ 
	visit(v0);
	visited[v0]=True; 
	p=g.vertex[v0].firstarc; 
	while( p!=NULL )    
	{ 
		if (! visited[p->adjvex])      
		DepthFirstSearch(g,  p->adjvex);    
		p=p->nextarc;    
	} 
}/*DepthFirstSearch*/ 

采用邻接表的 DepthFirstSearch 算法

以邻接表作为存储结构,查找每个顶点的邻接点的时间复杂度为 O(e), 其 中 e 是无向图中的边数或有向图中弧数, 则深度优先搜索图的时间复杂度为 O(n+e)。

(3)用非递归过程实现深度优先搜索

算法思想
(1) 首先将 v0 入栈;
(2) 只要栈不空,则重复下述处理:

  • a) 栈顶顶点出栈,如果未访问,则访问并置访问标志;
  • b) 然后将 v0 所有未访问的邻接点入栈。

算法描述】 非递归形式的 DepthFirstSearch

void  DepthFirstSearch(Graph g,  int v0)   /* 从 v0 出发深度优先搜索图 g */ 
{ 
	InitStack(S);  /*初始化空栈*/ 
	Push(S, v0); 
	while ( ! Empty(S))   
	{ 
		v=Pop(S);   
		if (!visited[v])             /*栈中可能有重复顶点*/ 
		{ 
			visit(v);  
			visited[v]=True; 
		} 
		w= FirstAdjVertex (g, v);         /*求 v 的第一个邻接点*/ 
		while (w!=-1 )    
		{ 
			if (!visited[w])  
			Push(S, w); 
			w=NextAdjVertex (g, v, w);   /*求 v 相对于 w 的下一个邻接点*/ 
		}       
	} 
} 

2、广度优先搜索

广度优先搜索(Breadth_First Search)是指按照广度方向搜索,它类似于树 的层次遍历,是树的按层次遍历的推广。

广度优先搜索的基本思想是:

  • (1)从图中某个顶点 v0出发,首先访问 v0。
  • (2)依次访问 v0的各个未被访问的邻接点。
  • (3)分别从这些邻接点(端结点)出发,依次访问它们的各个未被访问的 邻接点(新的端结点)。访问时应保证:如果 Vi和 Vk为当前端结点,且 Vi在 Vk之前被访问,则 Vi的所有未被访问的邻接点应在 Vk的所有未被 访问的邻接点之前访问。重复(3),直到所有端结点均没有未被访问 的邻接点为止。

若此时还有顶点未被访问,则选一个未被访问的顶点作为起始点,重复上述过程,直至所有顶点均被访问过为止。

下图给出了一个广度优先搜索过程图示,其中箭头代表搜索方向,箭头旁边 的数字代表搜索顺序,A 为起始顶点。

首先访问 A,然后按图中序号对应的顺序进行广度优先搜索。图中序号对应 步骤的解释如下:

  • (1)顶点 A 的未访邻接点有 B、E、D,首先访问 A 的第一个未访邻接点 B;
  • (2)访问 A 的第二个未访邻接点 E;
  • (3)访问 A 的第三个未访邻接点 D;
  • (4)由于 B 在 E、D 之前被访问,故接下来应访问 B 的未访邻接点。B 的未访邻接点只有 C,所以访问 C;
  • (5)由于 E 在 D、C 之前被访问,故接下来应访问 E 的未访邻接点。E 的未访邻接点只有 G,所以访问 G;
  • (6)由于 D 在 C、G 之前被访问,故接下来应访问 D 的未访邻接点。D 没有未访邻接点,所以直接考虑在 D 之后被访问的顶点 C,即接下 来应访问 C 的未访邻接点。C 的未访邻接点只有 F,所以访问 F;
  • (7)由于 G 在 F 之前被访问,故接下来应访问 G 的未访邻接点。G 的未 访邻接点只有 H,所以访问 H;
  • (8)由于 F 在 H 之前被访问,故接下来应访问 F 的未访邻接点。F 没有 未访邻接点,所以直接考虑在 F 之后被访问的顶点 H,即接下来应 访问 H 的未访邻接点。H 的未访邻接点只有 I,所以访问 I。

至此,广度优先搜索过程结束,相应的访问序列为:A、B、E、D、C、G、 F、H、I。
在这里插入图片描述
在遍历过程中需要设立一个访问标志数组 visited[n],其初值为“False”,一 旦某个顶点被访问,则置相应的分量为“True”。同时,需要辅助队列 Q,以便 实现要求:“如果 Vi和 Vk为当前端结点,且 Vi在 Vk之前被访问,则 Vi的所有 未被访问的邻接点应在 Vk的所有未被访问的邻接点之前访问。”

广度优先搜索连通子图的算法如下:
算法思想

  • (1) 首先访问 v0 并置访问标志,然后将 v0 入队;
  • (2) 只要队不空,则重复下述处理: ①队头结点 v 出队; ②对 v 的所有邻接点 w,如果 w 未访问,则访问 w 并置访问标志,然后将 w 入队。

算法描述】 广度优先搜索图 g 中 v0 所在的连通子图

void  BreadthFirstSearch(Graph g,  int v0)   /*广度优先搜索图 g 中 v0 所在的连通子图*/ 
{ 
	visit(v0);  
	visited[v0]=True; 
	InitQueue(&Q);  /*初始化空队*/  
	EnterQueue(&Q, v0);  /* v0 进队*/ 
	while ( ! Empty(Q))   
	{ 
		DeleteQueue(&Q, &v);  /*队头元素出队*/ 
		w=FirstAdjVertex (g, v);  /*求 v 的第一个邻接点*/ 
		while (w!=-1)    
		{ 
			if (!visited[w])    
			{ 
				visit(w);  
				visited[w]=True;                      
				EnterQueue(&Q, w);                    
			} 
			w=NextAdjVertex (g, v, w);  /*求 v 相对于 w 的下一个邻接点*/    
		}       
	}   
} 

分析上述算法,图中每个顶点至多入队一次,因此外循环次数为 n。当图 g 采用邻接表方式存储,则当结点 v 出队后,内循环次数等于结点 v 的度。对访问 所有顶点的邻接点的总的时间复杂度为 O(d0+d1+d2+…+dn-1)=O(e),因此图采用 邻接表方式存储,广度优先搜索算法的时间复杂度为 O(n+e);当图 g 采用邻接 矩阵方式存储,由于找每个顶点的邻接点时,内循环次数等于 n,因此广度优先 搜索算法的时间复杂度为 O(n²)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值