图的遍历—深度优先搜索和广度优先搜索


前言

深度优先搜索和广度优先搜索这两种图的遍历方式是求解图的连通性问题、拓扑排序和关键路径等算法的基础,应该要熟练掌握这两种遍历方式的选择以及应用。若对文章内容有疑惑的,欢迎评论区发言,小白会积极改进的。


一、图的遍历

遍历图:
就是对图中的所有顶点进行访问并且访问一次,因为遍历图那就是要访问所有的顶点并且没有必要重复访问同一个顶点。
访问标志数组visited[n]:
visited[i]:用来标记顶点i是否被访问过,初始值为“false”或0,当顶点vi被访问过后,将visited[i]赋值为“false”或1。
为什么要有访问标志数组?
答:标记数组的存在是用来标记此顶点有没有遍历过,如果遍历过,则跳过;如果没遍历过,则进行遍历,遍历之后把该节点标记为访问过,这样可以避免重复遍历已经遍历过的节点
首先我们要明白在图结构中,节点之间的关系可以是任意的,任意两个节点之间可能都会有关系,所以图中出现环,一点也不意外,如图示:
在这里插入图片描述
遍历图,要求把每个顶点都遍历了,并且每个节点只遍历一次。但是对于上面的图,在访问了某点后,可能沿着某条路径搜索之后,又回到该点上,这样就会重复遍历已经遍历过的点。例如,在一种可能访问路中,从顶点v1出发,之后访问v2、v4、v8、v5、v2,可以看到v2是重复访问过了的。那么我们就需要标记数组来告诉我们此节点已经访问了,我们就可以不用遍历它了。
根据搜索路径的方向,通常有两条遍历图的路径:深度优先搜索和广度优先搜索。它们对无向图和有向图均适用。

二、深度优先搜索

1.深度优先搜索的遍历过程

深度优先搜索(Depth First Search,DFS)遍历类似于树的先序遍历,是树的先序遍历的推广。
树的先序遍历:
若树非空,则操作为空;否则:
(1)访问根节点;
(2)先序遍历左子树
(3)先序遍历右子树
首先访问根节点,然后递归地先序遍历左子树,最后递归地先序遍历右子树。
如图示:
5
该树的遍历过程:
遍历v1节点
遍历v1节点的左子节点v2
遍历v2的左子节点v3
v3没有子节点则回溯到v2,遍历v2的右子节点v4
v4没有子节点则回溯到v1,遍历v1的右子节点v5
v5没有左子节点,遍历v5的右子节点v6
v6没有子节点,回溯到v1遍历结束
总遍历过程为:v1-v2-v3-v4-v5-v6

1.图的深度优先搜索过程

从字面了解深度优先搜索,深度优先,就是从一个顶点一直沿着某条路径遍历到底部(没有叶子的节点),走到头之后又退回到上一个节点的其他支路继续进行搜索一种搜索策略。
就如走迷宫,随机选择一个岔路口走,之后会有很多岔路口,然后又随机进入一个岔路口,就这样一直走下去,直到走出迷宫或者走到尽头没有路走,就退回到上一个岔路口,走另一条路,往复下去,直到走到出口。
深度优先搜索遍历过程
(1)从图中某个顶点v出发,访问v。
(2)找出刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新顶点,重复此步骤,直至刚访问过的顶点没有被访问的邻接点为止。
(3)返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点。
(4)重复步骤(2)和步骤(3),直至图中所有顶点都被访问过,搜索结束。

2.深度优先搜索的算法实现

1.深度优先搜索遍历连通图

如果此图是连通图,那么从任意一点开始搜索都可以遍历完所有的顶点。

1.采用邻接矩阵表示图的深度优先搜索遍历

dfs函数在代码最后面

#define MaxNum 100	// 最大顶点数,因为后边有个表头节点数组
int visited[MaxNum];

typedef struct {
	char vexs[MaxNum];	// 存储顶点信息的一维数组
	int arcs[MaxNum][MaxNum];	// 定义二维数组,表示邻接矩阵
	int vexnum, arcnum;	// 图的实际顶点数和边数
}AMGraph;

int LocateVex(AMGraph &G, int v) {	// 获取顶点v在图G中的位置
	for(int i = 0; i < G.vexnum; i++) {
		if(v == G.vexs[i]) {
			return i;
		}
	}
	return -1;
}

// 用邻接矩阵创建无向网,其实就是同通过对邻接矩阵的赋值操作,以表示顶点间的关系
void CreateUDN(AMGraph &G) {	// UDN表示无向网
	cin >> G.vexnum >> G.arcnum;	// 步骤一:输入总顶点数和总边数
	for(int i = 0; i < G.vexnum; i++) {	// 步骤二:初始化邻接矩阵,初始值默认为∞,我们用MaxInt表示
		for(int j = 0; j < G.arcnum; j++) {
			G.arcs[i][j] = MaxNum;
		}
	}
	int v1, v2, w;
	int i, j;
	for(int k = 0; k < G.vexnum; k++) {	// 步骤三:输入各边(输入这条边依附的两个顶点),并对邻接矩阵赋值(权值),依次表示顶点之间的关系
		cin >> v1 >> v2 >> w;
		i = LocateVex(G,v1);
		j = LocateVex(G, v2);
		G.arcs[i][j] = w;
		G.arcs[j][i] = w;	// 注意
	}
}

void dfs(AMGraph G, int v) {
	visited[v] = 1;
	for(int w = 0; w < G.vexnum; w++) {
		if(G.arcs[v][w] != 0 && (!visited[w])) {
			dfs(G, w);
		}
	}
}
2.采用邻接表表示图的深度优先搜索遍历

dfs函数在代码最后面

#define MaxNum 100	// 最大顶点数,因为后边有个表头节点数组
int visited[MaxNum];

typedef struct ArcNode {	// 边节点
	int adjvex;	// 邻接点域
	struct ArcNode * nextarc;	// 链域
	int info;	// 信息域
}ArcNode;
typedef struct VNode {
	char data;	// 数据域
	ArcNode *firstarc;	// 链域,指向一个ArcNode类型的指针。
}VNode,AdjList[MaxNum];
typedef struct {
	AdjList vertices;	// 它是AdjList类型的,即一个包含MaxNum个VNode结构体的数组
	int vexnum, arcnum;	// 当前图的顶点数和边数
}ALGraph;	// 邻接表英文:Adjacency List

int LocateVex(ALGraph &G, int v) {
	for(int i = 0; i < G.vexnum; i++) {
		if(v == G.vertices[i].data) {
			return i;
		}
	}
	return -1;
}

void CreateUDN(ALGraph & G) {
	cin >> G.vexnum >> G.arcnum;	// 步骤一:输入总顶点数和边数
	for(int i = 0; i < G.vexnum; i++) {	// 初始化邻接表
		cin >> G.vertices[i].data;
		G.vertices[i].firstarc = NULL;
	}
	int v1, v2;
	int i, j;
	for(int k = 0; k < G.arcnum; k++) {
		cin >> v1 >> v2;
		i = LocateVex(G, v1);
		j = LocateVex(G, v2);
		// 创建新的边节点
		ArcNode *p1 = new ArcNode; // 分配内存并创建节点
		p1->adjvex = j;
		p1->nextarc = G.vertices[k].firstarc;
		G.vertices[i].firstarc = p1;
		ArcNode *p2 = new ArcNode;
		p2->adjvex = i;
		p2->nextarc = G.vertices[j].firstarc;
		G.vertices[i].firstarc = p2;
	}
}

void dfs(ALGraph G, int v) {
	visited[v] = 1;
	ArcNode *p = G.vertices[v].firstarc;
	while(p != NULL) {
		int w = p->adjvex;
		if(!visited[w]) {
			dfs(G, w);
		}
		p = p->nextarc;
	}
}
2.深度优先遍历非连通图

如果是非连通图,在上诉遍历过程执行后,图中一定还有顶点未被访问,需要从图中另选一个未被访问的顶点作为起始点,重复上述深度优先搜索过程,直到图中所有顶点均被访问过为止。
代码:

for(int v = 0; v < G.vexnum; ++v) {
		visited[v] = false;
	}
	for(int v = 0; v < G.vexnum; ++v) {
		if(!visited[v])
			dfs(G, v);
	}
3.深度优先搜索遍历的算法分析

当用邻接矩阵表示图时,查找每个顶点的邻接点的时间复杂度为O(n^2),其中n为图中顶点数。而当以邻接表作为图的存储结构时,查找邻接点的时间复杂度为O(e),其中e为图中边数。

2.广度优先搜索

1.广度优先搜索遍历的过程

广度优先搜索(Breadth First Search,BFS)遍历类似于树的按层次遍历的过程。
广度优先搜索遍历的过程如下:
(1)从图中某个顶点v出发,访问v。
(2)依次访问v的各个未曾访问过的邻接点。
(3)分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。重复步骤3,直至图中所有已被访问的顶点的邻接点都被访问到。

2.广度优先搜索遍历的算法实现

广度优先搜索遍历的特点是:尽可能先对横向进行搜索。设x和y是两个相继被访问过的顶点,若当前以x为出发点进行搜索,则在访问x的所有未曾被访问过的邻接点之后,紧接着以y为出发点进行横向搜索,并对搜索到的y的邻接点中尚未被访问的顶点进行访问。也就是说,先访问的顶点其邻接点亦先被访问。为此,算法实现时需引进队列保存已被访问过的顶点。

void bfs(Graph G, int v) {
	visited[v] = true;
	InitQueue(Q);
	EnQueue(Q,v);
	while(!QueueEmpty(Q)) {
		DeQueue(G, u);
		for(int w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, u, w)) {// w >= 0表示存在邻接点
			if(!visited[w]) {
				visited[w] = true;
				EnQueue(Q, w);
			}
		}
	}
}

对于非连通图的遍历,实现算法类似于深度优先搜索遍历中的非连通图的遍历,只需把dfs改为bfs。

3.广度优先搜索遍历的算法分析

当用邻接矩阵表示图时,查找每个顶点的邻接点的时间复杂度为O(n^2),其中n为图中顶点数。而当以邻接表作为图的存储结构时,查找邻接点的时间复杂度为O(e),其中e为图中边数。


总结

我下班了,蟹老板。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值