《大话数据结构》笔记——第7章 图(二)

声明:

本博客是本人在学习《大话数据结构》后整理的笔记,旨在方便复习和回顾,并非用作商业用途。

本博客已标明出处,如有侵权请告知,马上删除。

7.5 图的遍历

图的遍历是和树的遍历类似,我们希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历(Traversing Graph)

树的遍历我们谈到了四种方案,应该说都还好,毕竟根结点只有一个,遍历都是从它发起,其余所有结点都只有一个双亲。可图就复杂多了,因为它的任一顶点都可能和其余的所有顶点相邻接,极有可能存在沿着某条路径搜索后,又回到原顶点,而有些顶点却还没有遍历到的情况。因此我们需要在遍历过程中把访问过的顶点打上标记,以避免访问多次而不自知。具体办法是设置一个访问数组 visited[n],n 是图中顶点的个数,初值为 0 ,访问过后设置为 1。这其实在小说中常常见到,一行人在迷宫中迷了路,为了避免找寻出路时屡次重复,所以会在路口用小刀刻上标记。

对于图的遍历来说,如何避免因回路陷入死循环,就需要科学地设计遍历方案, 通常有两种遍历次序方案:它们是深度优先遍历和广度优先遍历

7.5.1 深度优先遍历

深度优先遍历(Depth_First_Search),也有称为深度优先搜索,简称为 DFS

为了更好的理解深度优先遍历,我们来做一个游戏。

假设你需要完成一个任务,要求你在如图 7-5-2 左图这样的一个迷宫中,从顶点 A开始要走遍所有的图顶点并作上标记,注意不是简单地看着这样的平面图走哦,而是如同现实般地在只有高墙和通道的迷宫中去完成任务。

在这里插入图片描述

很显然我们是需要策略的,否则在这四通八达的通道中乱窜,要想完成任务那就只能是碰运气。如果你学过深度优先遍历,这个任务就不难完成了。

首先我们从顶点 A 开始,做上表示走过的记号后,面前有两条路,通向 B 和 F , 我们给自己定一个原则,在没有碰到重复顶点的情况下,始终是向右手边走,于是走到了 B 顶点。整个行路过程,可参看图 7-5-2 右图。此时发现有三条分支,分別通向顶点 C、I、G,右手通行原则,使得我们走到了 C 顶点。就这样,我们一直顺着右手通道走,一直走到 F 顶点。当我们依然选择右手通道走过去后,发现走回到顶点 A 了,因为在这里做了记号表示已经走过。此时我们退回到顶点 F,走向从右数的第二条通道,到了 G 顶点,它有三条通道,发现 B 和 D 都已经是走过的,于是走到 H ,当我们面对通向 H 的两条通道 D 和 E 时,会发现都已经走过了。

此时我们是否已经遍历了所有顶点呢?没有。可能还有很多分支的顶点我们没有走到,所以我们按原路返回。在顶点 H 处,再无通道没走过,返回到 G,也无未走过通道,返回到 F,没有通道,返回到 E,有一条通道通往 H 的通道,验证后也是走过的,再返回到顶点 D,此时还有三条道未走过,一条条来,H 走过了,G 走过了,I,哦,这是一个新顶点,没有标记,赶快记下来。继续返回,直到返回顶点 A ,确认你已经完成遍历任务,找到了所有的 9 个顶点。

深度优先遍历其实就是一个递归的过程,如果再仔细观察,就会发现其实转换成下图所示后,就像是一棵树的前序遍历,没错, 它就是。它从图中某个顶点 v 出发,访问此顶点,然后从 v 的未被访问的邻接点出发深度优先遍历图,直至图中所有和 v 有路径相通的顶点都被访问到。事实上,我们这里讲到的是连通图,对于非连通图,只需要对它的连通分量分别进行深度优先遍历,即在先前一个顶点进行一次深度优先遍历后,若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止

如果我们用的是邻接矩阵的方式,则代码如下:

#define MAX 100
#define TRUE  1
#define FALSE 0
typedef int Boolean;	/* Boolean 是布尔类型,其值是 TRUE 或 FALSE */
Boolean visited[MAX];	/* 访问标志的数组 */
/* 邻接矩阵的深度优先递归算法 */ 
void DFS(MGraph G, int i)
{
   
	int j;
	visited[i] = TRUE;
	printf("%c ", G.vexs[i]); /* 打印頂点,也可以其他操作 */
	for (j = 0; j < G.numVertexes; j++)
	{
   
		if (G.arc[i][j] == 1 && !visited[j])
		{
   
			DFS(G, j);	/*对为访问的邻接顶点递归调用*/
		}
	}
 
}
/*邻接矩阵的深度遍历操作*/ 
void DFSTraverse(MGraph G)
{
   
	int i;
	for (i = 0; i < G.numVertexes; i++)
	{
   
		visited[i] = FALSE;	/*初始所有顶点状态都是未访问过状态*/
	}
	for (i = 0; i < G.numVertexes; i++)
	{
   
		if (!visited[i]) /* 对未访问过的顶点调用 DFS , 若是连通图,只会执行一次*/
		{
   
			DFS(G, i);
		}
	}
							
}

代码的执行过程,其实就是我们刚才迷宫找寻所有顶点的过程。

如果图结构是邻接表结构,其 DFSTraverse 函数的代码是几乎相同的,只是在递归函数中因为将数组换成了链表而有不同,代码如下:

/*邻接表的深度优先递归算法*/ 
void DFS(GraphAdjList GL, int i)
{
   
	EdgeNode *p; 
	visited[i] = TRUE;
	printf("%c ", GL.adjList[i].data); /* 打印顶点,也可以其他操作 */
	p = GL.adjList[i].firstedge; 
	while (p)
	{
   
		if (!visited[p->adjvex])
		{
   
			DFS(GL, p->adjvex); /*对为访问的邻接頂点递归调用*/
		}
		p = p->next;
	}
}
/* 邻接表的深度遍历操作 */
void DFSTraverse(GraphAdjList GL)
{
   
	int i;
	for (i = 0; i < GL.numVertexes; i++)
	{
   
		visited[i] = FALSE; /*初始所有顶点狀态都是未访问过狀态*/
	}
	for (i = 0; i < GL.numVertexes; i++)
	{
   
		if (!visited[i]) /*对未访问过的顶点调用DFS,若是连通图,只会执行一次*/
		{
   
			DFS(GL, i);
		}
	}
}

对比两个不同存储结构的深度优先遍历算法,对于 n 个顶点 e 条边的图来说,邻接矩阵由于是二维数组,要查找每个顶点的邻接点需要访问矩阵中的所有元素,因此都需要 O(n^2) 的时间。而邻接表做存储结构时,找邻接点所需的时间取决于顶点和边 的数量,所以是 O(n+e) 。显然对于点多边少的稀疏图来说,邻接表结构使得算法在时间效率上大大提高。

对于有向图而言,由于它只是对通道存在可行或不可行,算法上没有变化,是完全可以通用的,这里就不再详述了。

7.5.2 广度优先遍历

广度优先遍历(Breadth_First_SearCh),又称为广度优先搜索,简称 BFS

如果说图的深度优先遍历类似树的前序遍历,那么图的广度优先遍历就类似于树的层序遍历了。我们将下图的第一幅图稍微变形,变形原则是顶点 A 放置在最上第一层,让与它有边的顶点 B 、 F 为第二层,再让与 B 和 F 有边的顶点 C 、 I 、 G 、E 为第三层,再将这四个顶点有边的 D 、 H 放在第四层,如下图的第二幅图所示。 此时在视觉上感觉图的形状发生了变化,其实顶点和边的关系还是完全相同的。

在这里插入图片描述

有了这个讲解,看代码就非常容易了。以下是邻接矩阵结构的广度优先遍历算法。

/*邻接矩阵的广度遍历算法*/ 
void BFSTraverse(MGraph G)
{
   
	int i, j;
	Queue Q;
	for (i = 0; i < G.numVertexes; i++)
	{
   
		visited[i] = FALSE;
	}
	InitQueue(&Q);	/*初始化一辅助用的队列*/
	for (i = 0; i < G.numVertexes; i++)	/* 对每一个顶点做循环 */
	{
   
		if (!visited[i])	/*若是未访问过就处理*/
		{
   
			visited[i] = TRUE;	/*设置当前顶点访问过*/
			printf("%c ", G.vexs[i]); /*打印顶点,也可以其他操作*/
			EnQueue(&Q, i);	/*将此顶点入队列*/
			while (!QueueEmpty(Q))/* 若当前队列不为空 */
			{
   
				DeQueue(&Q, &i);	/*将队中元素出队列,赋值给 i */
				for (j = 0; j < G.numVertexes; j++)
				{
   
					/*判断其他顶点若与当前顶点存在边且未访问过*/
					if (G.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bm1998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值