数据结构之图的遍历


  数据结构是程序设计的重要基础,它所讨论的内容和技术对从事软件项目的开发有重要作用。学习数据结构要达到的目标是学会从问题出发,分析和研究计算机加工的数据的特性,以便为应用所涉及的数据选择适当的逻辑结构、存储结构及其相应的操作方法,为提高利用计算机解决问题的效率服务。
  数据结构是指数据元素的集合及元素间的相互关系和构造方法。元素之间的相互关系是数据的 逻辑结构,数据元素及元素之间关系的存储称为 存储结构(或物理结构)。数据结构按照逻辑关系的不同分为 线性结构非线性结构两大类,其中,非线性结构又可分为树结构和图结构。
  图是比树结构更复杂的一种数据结构。在线性结构中,除首结点没有前驱、末尾结点没有后继外,一个结点只有唯一的一个直接前驱和唯一的一个直接后继。在树结构中,除根结点没有前驱结点外,其余的每个结点只有唯一的一个前驱(双亲) 结点和多个后继 (子树) 结点。而在图中,任意两个结点之间都可能有直接的关系,所以图中一个结点的前驱结点和后继结点的数目是没有限制的。
  图的遍历是指从某个顶点出发,沿着某条搜索路径对图中的所有顶点进行访问且只访问一次的过程。图的遍历算法是求解图的连通性问题、拓扑排序及求关键路径等算法的基础。
  图的遍历要比树的遍历复杂得多。因为图的任一个结点都可能与其余顶点相邻接,所以在访问了某个顶点之后,可能沿着某路径又回到该结点上,为了避免对顶点进行重复访问,在图的遍历过程中必须记下每个已访问过的顶点。深度优先搜索和广度优先搜索是两种遍历图的基本方法。

1、深度优先搜索 (Depth First Search,DFS)

  此种方法类似于树的先根遍历,在第一次经过一个顶点时就进行访问操作。从图 G 中任一结点v出发按深度优先搜索法进行遍历的步骤如下。
  (1)设置搜索指针 p,使p 指向顶点 v。
  (2)访问 p 所指顶点,并使 p 指向与其相邻接的且尚未被访问过的顶点。
  (3) 若p所指顶点存在,则重复步骤 (2),否则执行步骤(4)。
  (4)沿着刚才访问的次序和方向回溯到一个尚有邻接顶点且未被访问过的顶点,并使p指向这个未被访问的顶点,然后重复步骤(2),直到所有的顶点均被访问为止。
  该算法的特点是尽可能先对纵深方向搜索,因此可以得到其递归遍历算法。
  【函数】以邻接链表表示图的深度优先搜索算法。

int visited[MaxN] = {
   0};			/*调用遍历算法前设置所有的顶点都没有被访问过*/
void Dfs(Graph G, int i) {
   
	EdgeNode *t; int j;
	printf("%d", i);				/*访问序号为i的顶点*/
	visited[i] = 1;					/*序号为i的顶点已被访问过*/
	t = G.Vertices[i].firstarc;		/*取顶点i的第一个邻接顶点*/
	while(t != NULL){
   				/*检查所有与顶点i相邻接的顶点*/
		i= t->adjvex;				/*顶点j为顶点i的一个邻接顶点*/
		if(visited[j] == 0)			/*若顶点j未被访问则从顶点 j 出发进行深度优先搜索*/
			Dfs(G,j);
		t= t->nextarc;				/*取顶点i的下一个邻接顶点*/
	}/*while*/
}/*Dfs*/

  从函数 Dfs() 之外调用 Dfs 可以访问到所有与起始顶点有路径相通的其他顶点。若图是不连通的,则下一次应从另一个未被访问过的顶点出发,再次调用 Dfs 进行遍历,直到将图中所有的顶点都访问到为止。深度优先的搜索过程如下图所示。
在这里插入图片描述

  深度优先遍历图的过程实质上是对某个顶点查找其邻接点的过程,其耗费的时间取决于所采用的存储结构。当图用邻接矩阵表示时,查找所有顶点的邻接点所需时间为 O(n2)。若以邻接表作为图的存储结构,则需要 O(e)的时间复杂度查找所有顶点的邻接点。因此,当以邻接表作为存储结构时,深度优先搜索遍历图的时间复杂度为 O(n+e)。

2、广度优先搜索(Breadth First Search,BFS)

  图的广度优先搜索方法为:从图中的某个顶点v出发,在访问了v之后依次访问 v 的各个未被访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直到图中所有已被访问的顶点的邻接点都被访问到。若此时还有未被访问的顶点,则另选图中的一个未被访问的顶点作为起点,重复上述过程,直到图中所有的顶点都被访问到为止。对下图所示的图进行广度优先搜索得到的序列为“1,2,3,4,5,6”。
在这里插入图片描述

  广度优先遍历图的特点是尽可能先进行横向搜索,即最先访问的顶点的邻接点也先被访问。为此,引入队列来保存已访问过的顶点序列,即每当一个顶点被访问后,就将其放入队列中,当队头顶点出队时,就访问其未被访问的邻接点并令这些邻接顶点入队。
  【算法】以邻接链表表示图的广度优先搜索算法。

void Bfs(Graph G)
{
   	/*广度优先遍历图G*/
	EdgeNode *t; int i,j,k;
	int visited[MaxN] = {
   0);										/*调用遍历算法前设置所有的顶点都没有被访问过*/
	InitOueue(Q);													/*创建一个空队列*/
	for(i=0; i<G.Vnum; i++){
   
		if (!visited[i]){
   											/*顶点i未被访问过*/
			EnQueue(Q,i);
			printf("%d ",i); visited[i]=l;							/*访问顶点i并设置已访问标志*/
			while(!isEmpty(Q)){
   										/*若队列不空,则继续取顶点进行广度优先搜索*/
				DeQuque(Q,k);
				t = G.Vertices(k].firstarc;
				for(; t; t= t->nextarc){
   							/*检查所有与顶点 k 相邻接的顶点*/
					j= t->adjvex;									/*顶点j是顶点k 的一个邻接顶点*/
					if (visitedl[j] == 0){
   							/*若顶点j未被访问过,将j加入队列*/
						EnQueue(Q,j);
						printf("%d ", j);							/*访问序号为i的顶点并设置已访问标志*/
						visited[j] = 1;
					}/*if*/
				}/*for*/
			}/*while*/
		}/*if*/
	}/*for i*/
)/*Bfs*/

  在广度优先遍历算法中,每个顶点最多进一次队列。
  遍历图的过程实质上是通过边或弧找邻接点的过程,因此广度优先搜索遍历图和深度优先搜索遍历图的运算时间复杂度相同,其不同之处仅仅在于对顶点访问的次序不同。

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-01-27 14:54:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-27 14:54:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-27 14:54:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-27 14:54:02       18 阅读

热门阅读

  1. ReactHooks 官网文档翻译

    2024-01-27 14:54:02       24 阅读
  2. react-jss书写样式

    2024-01-27 14:54:02       37 阅读
  3. docker基础

    2024-01-27 14:54:02       34 阅读
  4. GBASE南大通用分享:clickhouse-Merge Tree原理解析

    2024-01-27 14:54:02       34 阅读
  5. SpringMVC RESTful风格

    2024-01-27 14:54:02       33 阅读
  6. STL标准库(三)序列容器之list

    2024-01-27 14:54:02       34 阅读
  7. PMM深入研究:如何自定义配置和集成到外部grafana

    2024-01-27 14:54:02       36 阅读
  8. Unity 中的接口和继承

    2024-01-27 14:54:02       28 阅读
  9. Spring中的以Aware结尾的接口是做什么的?

    2024-01-27 14:54:02       33 阅读
  10. 数据结构与算法面试系列-01

    2024-01-27 14:54:02       30 阅读