0
点赞
收藏
分享

微信扫一扫

领接矩阵结构的图的遍历(广度和深度遍历)

Android开发指南 2022-04-13 阅读 80

最近在数据结构的学习中,学习到了图这一章节,在图中,有两个遍历的策略:一种是广度优先遍历,一种是深度优先遍历

先上运行结果吧

 

图的结构

 这里以D顶点为起始顶点

 

广度优先遍历        

首先我们介绍广度优先遍历,在介绍图的广度优先遍历之前,先回顾一下树的广度优先遍历,就是层序遍历。下面是树的层序遍历代码

void LevelOrder(BitTree t) {
	Linkqueue q;//声明一个链队列
	initQueue(q);//初始化一个链队列

	BitTree p;
	enqueue(q, t);//把根节点入队
	while (!isEmpty(q)) {//队列不空就循环
		dequeue(q, p);//队头结点出队
		visit(p);//访问出队结点
		if (p->lchild != NULL)
			enqueue(q, p->lchild);//左孩子入队
		if (p->rchild != NULL)
			enqueue(q, p->rchild);//右孩子入队
	}
}

所以,我们还需要设置一个标记数组,记作visited,这个数组和图的顶点序列数组一致,数组的每一项都对应了一个顶点,设置bool值来反应,通过索引值可以比较方便判断顶点是否被遍历过。

bool visited[MAXSIZE];//全局

在开始之前,我们还需要两个函数,一个函数用来求x顶点的第一个和它直接连通的顶点一个函数用来求除开y顶点,和x顶点直接连通的顶点

//求图G中顶点x的第一个邻接点,若有则返回顶点编号,若没有则返回 - 1。
int FirstNeighbor(Graph g, char vertex) {
	if ((vertex - 65) < 0 || (vertex - 65) >= MAXSIZE) {
		printf("输入字符不合法!\n");
		return -1;
	}
	int ver_index = -1;
	for (int i = 0; i < g.vertex_num; i++)
	{
		if (vertex == g.vertexs[i].ch) {
			ver_index = i;//找到该顶点的索引.
		}
	}
	if (ver_index == -1) {
		return -1;//说明该顶点不存在
	}
	else {
		for (int i = 0; i < g.vertex_num; i++)
		{
			if (g.edge[ver_index][i] != 0) {
				return i;
			}
		}
	}
	return -1;
}
//求图G中顶点y是顶点x的一个邻接点,返回除了y之外的下一个和x邻接的顶点,如果y是唯一和x邻接的顶点,返回-1.
int NextNeighbor(Graph g, char ver_x, char ver_y) {
	//超出A-Z的范围
	if ((ver_x - 65) < 0 || (ver_x - 65) > 26) {
		return -1;
	}
	int ver_index = -1;
	int target_index = -1;
	for (int i = 0; i < g.vertex_num; i++)
	{
		if (g.vertexs[i].ch == ver_x) {
			ver_index = i;//获取x结点对应的数组下标
		}
		if (g.vertexs[i].ch == ver_y) {
			target_index = i;//获取y结点对应的数组下标
		}
	}
	if (ver_index == -1) {
		return -1;//表示图中没有找到输入的x顶点
	}
	if (target_index == -1) {
		return -1;
	}

	for (int i = target_index + 1; i < g.vertex_num; i++)
	{
		if (g.edge[ver_index][i] != 0) {
			//表示找到目标y结点
			return i;
		}
	}
	return -1;
}

这两个函数当返回值>=0时,便表示找到了目标顶点,并返回目标顶点在顶点数组的下标

接下来就是广度优先遍历的主体函数

//图的广度优先遍历
bool BFSGraph(Graph g,char v) {
	if (g.vertex_num == 0) {
		return false;
	}
	char queue[30];//定义一个队列
	int front = -1, rear = -1;//定义队列的指针

	int v_index = -1;//表示v在vertexs数组的索引值
	//获取该节点对应的index值
	for (int i = 0; i < g.vertex_num; i++)
	{
		if (v == g.vertexs[i].ch) {
			v_index = i;
		}
	}
	if (v_index == -1) {
		//表示没有找到对应的结点
		return false;
	}

	VisitVertex(g.vertexs[v_index].ch);//访问第一个结点
	visited[v_index] = true;//把对应的顶点的标记数组标记为真,表示访问过该顶点

	rear++;
	queue[rear] = v;//把该顶点入队
	while (front != rear) {
		
		front++;//出队
		v = queue[front];//v等于出队顶点

		for (int w_index = FirstNeighbor(g,v); w_index >= 0; w_index = NextNeighbor(g,v,g.vertexs[w_index].ch))
		{
			//FirstNeighbor是返回对应顶点的第一个邻接点在vertex中的索引。
			if (!visited[w_index]) {
				//若该顶点还没有被访问
				VisitVertex(g.vertexs[w_index].ch);
				visited[w_index] = true;
				
				rear++;
				queue[rear] = g.vertexs[w_index].ch;
			}
		}
	}
	return true;
}

到这里,还没有完全结束,因为图中,两个顶点不一定一定有通路,所以我们还需要把图中所有顶点全部遍历到,这时候,我们发现在visited数组中,还有一些数组元素的值仍然是false,这些顶点和你所输入的顶点并没有通路,所以函数没有遍历到,所以,我们再使用一个函数,遍历的去访问这些false的顶点。

//图的广度优先遍历的
void BFSGraphTraverse(Graph g,char v) {
	for (int i = 0; i < g.vertex_num; i++)
		visited[i] = false;//先把标记数组的值设置为false,表示没有遍历过的顶点
	for (int i = 0; i < g.vertex_num; i++)
	{
		if (!visited[i]) {
			BFSGraph(g, v);//这个顶点没有被访问就调用
		}
	}
}

这也是最终的入口函数

深度优先遍历

在树的深度优先遍历中,使用的先根遍历的方式,把这种思想迁移到图的深度遍历中,在这里,与广度遍历一样的是,也是准备一个visited数组,去避免回路的重复访问相同顶点,下面就是具体代码

//图的深度优先
void DFSGraphTraverse(Graph g,char v) {
	VisitVertex(v);//访问v顶点
	int v_index = -1;
	//获取该节点对应的index值
	for (int i = 0; i < g.vertex_num; i++)
	{
		if (v == g.vertexs[i].ch) {
			v_index = i;
		}
	}
	visited[v_index] = true;
	for (int w_index = FirstNeighbor(g, v); w_index >= 0; w_index = NextNeighbor(g, v, g.vertexs[w_index].ch))
	{
		//FirstNeighbor是返回对应顶点的第一个邻接点在vertex中的索引。
		if (!visited[w_index]) {
			DFSGraphTraverse(g, g.vertexs[w_index].ch);
		}
	}

}

广度优先算法的性能分析,这里考虑主体的功能实现的性能,不考虑其他一些操作

  • 邻接矩阵存储的图中,访问|V|个顶点需要O(|V|)的时间,查找每一个顶点的邻接点都需要O(|V|)的时间,所以这个算法的时间复杂度是O(|V|²)

  • 邻接表存储的图中,访问|V|个顶点需要O(|V|)的时间,查找每一个顶点的邻接点一共需要O(|E|)的时间,所以这个算法的时间复杂度是O(|V| + |E|)

具体解释一下这段代码

for (int w_index = FirstNeighbor(g,v); w_index >= 0; w_index = NextNeighbor(g,v,g.vertexs[w_index].ch))
		{
			//FirstNeighbor是返回对应顶点的第一个邻接点在vertex中的索引。
			if (!visited[w_index]) {
				//若该顶点还没有被访问
				VisitVertex(g.vertexs[w_index].ch);
				visited[w_index] = true;
				
				rear++;
				queue[rear] = g.vertexs[w_index].ch;
			}
		}

FirstNeighbor函数是用用来返回v顶点的第一个直接连通的顶点的数组下标,g.vertexts[w_index]就是对应的顶点,NextNeighbor函数是返回除开g.vertexts[w_index]顶点的下一个v顶点直接连通的顶点数组下标通过这个for循环,会一直循环返回出v结点的所有相连通的顶点的数组下标,一直到函数返回-1,表示没有顶点和v顶点连通为止。

其实不管是广度优先还是深度优先,图的遍历一定上和树的遍历都是一样的,因为图和树其实也是有很多相似的地方,主要在于理解树的先根遍历,层次遍历,理解队列的配合使用和函数的递归调用,这些才是本质。

我所定义的图的结构

//定义图的顶点
typedef struct Vertex {
	int data;//顶点的数值
	char ch;//顶点的字符代号
	int info;//顶点的权
	bool empty;//顶点是否存在
};


//定义邻接矩阵图
typedef struct Graph {
	Vertex vertexs[MAXSIZE];//存储结点
	int edge[MAXSIZE][MAXSIZE];//结点关系矩阵
	int vertex_num, arc_num;//图的当前顶点数和边数
};

//初始化矩阵图
void InitGraph(Graph &g) {
	for (int i = 0; i < MAXSIZE; i++)
	{
		g.vertexs[i].ch = 0;
	}
	//关系矩阵数据全部清零。
	for (int i = 0; i < MAXSIZE; i++)
	{
		for (int j = 0; j < MAXSIZE; j++) {
			g.edge[i][j] = 0;
		}
	}
	//基本状态清0
	g.vertex_num = 0;
	g.arc_num = 0;
}

VisitVertex函数

//访问图的顶点
void VisitVertex(char vertex) {
	cout << vertex ;
}
举报

相关推荐

0 条评论