educoder数据结构与算法 图 第1关:实现图的宽度优先遍历

任务描述

本关任务:请你实现 graph.cpp 里的int Graph_WidthFirst(Graph*g, int start, Edge* tree)函数。 注意遵守约定:编号小的优先入队列。

相关知识

图 2 给出了对图 1 的无向图的存储结构图:每个顶点的名称由一个字符串描述,所有字符串的起始地址组织为一个数组,数组的起始地址为vetex;顶点的相邻关系保存在相邻矩阵中,其起始地址为adjadj[i*n+j]的值为 1 表示i号顶点到j号顶点有边,为 0 表示无边,其中n是顶点个数,ij是顶点在顶点表中的编号。 将n,vetex,adj组织成结构:

 
  1. struct Graph {
  2. int n;//顶点数
  3. char** vetex;
  4. int* adj;
  5. };

给定指向该结构的指针g,就可以对图进行操作。

宽度优先遍历算法(伪代码):

 
  1. WidthFirst(Graph, start)
  2. //输入Graph是图,start是开始顶点的编号
  3. //输出:tree_edge[i]=<from,to>是遍历树的一条边
  4. //tree_edge[1..n-1]为遍历树的n-1条边
  5. //tree_edge[0].to … tree_edge[n-1].to是遍历序列
  6. QueueIn(<-1,start>)
  7. k=0;
  8. while(QueueNotEmpty) {
  9. <a,b>=QueueOut;
  10. if (unvisited(b)) {
  11. visit(b); // visit b, and set a flag for b.
  12. tree_edge[k++]=<a,b>; // add <a,b> to the tree
  13. for each <b,c> in the Edge Set {
  14. if (unvisited(c)) QueueIn(<b,c>); //约定:编号小的先入队列
  15. }
  16. }
  17. }

对图1运行该算法的结果: 生成树的边是:<-1,A> <A,B> <A,C> <A,F> <B,D> <F,E>; 宽度优先遍历的顶点访问次序是:A B C F D E。

编程要求

请你实现graph.cpp里的int Graph_WidthFirst(Graph*g, int start, Edge* tree)函数。 注意遵守约定:编号小的优先入队列。

 
  1. //Graph.cpp
  2. ///
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include "Graph.h"
  7. //
  8. Graph* Graph_Create(int n)
  9. {
  10. Graph* g=(Graph*)malloc(sizeof(Graph));
  11. g->n=n;
  12. g->vetex=(char**)malloc(sizeof(char*)*n);
  13. int i;
  14. for (i=0; i<n; i++) g->vetex[i] = NULL;
  15. g->adj=(int*)malloc(sizeof(int)*n*n);
  16. int j;
  17. for(i=0; i<n; i++) {
  18. for(j=0; j<n; j++) {
  19. g->adj[i*n+j]=0;
  20. }
  21. }
  22. return g;
  23. }
  24. void Graph_Free(Graph* g)
  25. {
  26. free(g->adj);
  27. int i;
  28. for (i=0; i<g->n; i++) free(g->vetex[i]);
  29. free(g->vetex);
  30. free(g);
  31. }
  32. int Graph_WidthFirst(Graph*g, int start, Edge* tree)
  33. //从start号顶点出发宽度优先遍历,(编号从0开始)
  34. //返回访问到的顶点数,
  35. //tree[]输出遍历树
  36. //返回的tree[0]是(-1, start),
  37. //真正的遍历树保存在tree[1..return-1], return是返回值
  38. //顶点的访问次序依次为tree[0].to, tree[1].to, ..., tree[return-1].to
  39. //输入时,tree[]的长度至少为顶点数
  40. //返回值是从start出发访问到的顶点数
  41. {
  42. const int MAX=1000;
  43. Edge queue[MAX];
  44. int head=0, tail=0;
  45. #define In__(a,b) {queue[tail].from=a; queue[tail].to=b; tail=(tail+1)%MAX;}/
  46. #define Out__(a,b) {a=queue[head].from; b=queue[head].to; head=(head+1)%MAX;}//
  47. #define QueueNotEmpty (head!=tail?1:0)///
  48. #define HasEdge(i,j) (g->adj[(i)*g->n+(j)]==1)
  49. char* visited=(char*)malloc(sizeof(char)*g->n);
  50. memset(visited, 0, sizeof(char)*g->n);
  51. int parent=-1;
  52. int curr=start;
  53. In__(parent, curr);
  54. int k=0; //已经访问的结点数
  55. //在begin和end之间实现你的代码
  56. /*****Begin*****/
  57. /*****End*****/
  58. free(visited);
  59. return k;
  60. #undef In__//
  61. #undef Out__///
  62. #undef QueueNotEmpty
  63. #undef HasEdge
  64. }

测试说明

本关的测试过程如下:

  1. 平台编译 step1/Main.cpp ;
  2. 平台运行该可执行文件,并以标准输入方式提供测试输入;
  3. 平台获取该可执行文件的输出,然后将其与预期输出对比,如果一致则测试通过;否则测试失败。

输入输出格式说明:

输入格式: 输入n,顶点数; 输入n个字符串,即n个顶点的名称,其编号按输入次序是,0,...,n-1; 输入若干数字对(a b)或<a b>(a b)表示无向边,<a b>表示有向边; 输入字符x,表示边输入结束; 输入一个数start,表示开始顶点的编号。

输出格式: 输出生成树的边序列,边的第start个顶点构成的序列应是顶点访问序列。

以下是平台对 step1/Main.cpp 的测试样例: 样例输入

 
  1. 6
  2. A
  3. B
  4. C
  5. D
  6. E
  7. F
  8. ( 0 1 )
  9. ( 0 2 )
  10. ( 0 5 )
  11. ( 1 3 )
  12. ( 1 5 )
  13. ( 2 3 )
  14. ( 4 5 )
  15. x
  16. 0

样例输出

 
  1. tree edges: <-1,A> <A,B> <A,C> <A,F> <B,D> <F,E>
  2. visit sequence: A B C F D E

下面是代码,复制可以给作者原力值,请多多支持我吧

//Graph
///
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Graph.h"
/

Graph* Graph_Create(int n)
{
	Graph* g=(Graph*)malloc(sizeof(Graph));
	g->n=n;
	g->vetex=(char**)malloc(sizeof(char*)*n);
	int i;
	for (i=0; i<n; i++) g->vetex[i] = NULL;
	g->adj=(int*)malloc(sizeof(int)*n*n);
	int j;
	for(i=0; i<n; i++) {
		for(j=0; j<n; j++) {
			g->adj[i*n+j]=0;
		}
	}
	return g;
}

void Graph_Free(Graph* g)
{
	free(g->adj);
	int i;
	for (i=0; i<g->n; i++) free(g->vetex[i]);
	free(g->vetex);
	free(g);
}

int Graph_WidthFirst(Graph*g, int start, Edge* tree)
//从start号顶点出发宽度优先遍历,(编号从0开始)
//返回访问到的顶点数,
//tree[]输出遍历树
//返回的tree[0]是(-1, start), 
//真正的遍历树保存在tree[1..return-1], return是返回值
//顶点的访问次序依次为tree[0].to, tree[1].to,  ..., tree[return-1].to
//输入时,tree[]的长度至少为顶点数
//返回值是从start出发访问到的顶点数
{
	const int MAX=1000;
	Edge queue[MAX];
	int head=0, tail=0;
#define In__(a,b)  {queue[tail].from=a; queue[tail].to=b; tail=(tail+1)%MAX;}/
#define Out__(a,b)  {a=queue[head].from; b=queue[head].to; head=(head+1)%MAX;}//
#define QueueNotEmpty (head!=tail?1:0)///
#define HasEdge(i,j)  (g->adj[(i)*g->n+(j)]==1)

	char* visited=(char*)malloc(sizeof(char)*g->n);
	memset(visited, 0, sizeof(char)*g->n);//memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法

	int parent=-1;  
	int curr=start;
	In__(parent, curr); 
	int k=0; //已经访问的结点数
	/*请在BEGIN和END之间实现你的代码*/
    /*****BEGIN*****/
    while(QueueNotEmpty)
	{
		Out__(parent,curr);//out是输出值,不需要预先给他值,当函数执行完毕后可以从这个变量获取输出的数据
		if(visited[curr])
		continue;
		visited[curr]=1;
		tree[k].from=parent;
		tree[k].to=curr;
		k++;
		int j;
		for(j=0;j<g->n;j++)
		{
			if(HasEdge(curr,j)&&!visited[j])
			In__(curr,j);
		}
	}
    /*****END*******/
	return k;
#undef In__//
#undef Out__///
#undef QueueNotEmpty
#undef HasEdge
}

  • 40
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
### 回答1: 邻接表是一种常用的的存储结构,它将每个顶点的邻接点列表存储在一个链表中。广度优先遍历算法是一种基于队列的遍历算法,它从的某个顶点开始,依次访问该顶点的所有邻接点,然后再依次访问这些邻接点的邻接点,以此类推,直到遍历完整个。 在邻接表存储的情况下,实现广度优先遍历算法的步骤如下: 1. 创建一个队列,将起始顶点入队。 2. 标记起始顶点为已访问。 3. 从队列中取出一个顶点,访问它的所有邻接点,并将未访问过的邻接点入队。 4. 标记所有已访问的邻接点为已访问。 5. 重复步骤3和步骤4,直到队列为空。 在实现过程中,可以使用一个数组来记录每个顶点是否已经被访问过,以避免重复访问。同时,可以使用一个指针数组来存储每个顶点的邻接点链表的头指针,以便快速访问每个顶点的邻接点。 综上所述,邻接表存储的广度优先遍历算法是一种基于队列的遍历算法,它可以快速遍历整个,并且可以避免重复访问。 ### 回答2: 广度优先遍历(BFS)是的一种基本遍历方式,它从指定的起始节点开始遍历,逐层地访问每个与该节点相邻的节点,直到遍历完整张。在的效率比较高的数据结构存储方法中,邻接表是最常用的,下面就为大家介绍用邻接表存储,并实现广度优先遍历算法的过程。 邻接表是一种用于表示某种数据结构,它将每个节点与它所连接到的所有节点连接成一个链表。实现邻接表存储的基本流程如下: (1)用结构体数组V存放顶点信息,结构体中至少包含节点名称和一个指针域,用来指向连接该节点的边表。 (2)建立一个链表数组E,数组大小为顶点个数,链表头指向存储该节点信息的结构体,链表节点指向与该节点相邻的节点。 (3)建立一个表示已经访问过的节点的数组visited,数组元素初始化为false。 (4)定义一个队列queue作为广度优先遍历的缓存,将起始节点加入队列。 (5)重复以下步骤直到队列为空 a)弹出队头元素,输出该元素; b)将刚才弹出的元素的所有邻居添加到队尾,如果邻居尚未被访问,则将其标记为访问过,并将其加入队列中。 具体代码实现如下: ```C++ #include <iostream> #include <queue> using namespace std; #define MAXV 100 //最大顶点数 struct EdgeNode{ int adjvex; //连接边的顶点编号 EdgeNode* next; //链表中下一个节点的地址 }; struct VertexNode{ int data; //顶点的编号或其它相信息 EdgeNode* firstEdge; //指向该节点的第一条边 }; VertexNode V[MAXV]; //存储顶点的数组 bool visited[MAXV] = {false};//记录节点是否被访问过 queue<int> Q; //定义一个队列存放待访问的节点 //添加一条有向边 void addEdge(int u, int v){ EdgeNode* e = new EdgeNode; e->adjvex = v; e->next = V[u].firstEdge; V[u].firstEdge = e; } //广度优先遍历 void BFS(int v){ visited[v] = true; //标记节点v为已访问 Q.push(v); //将节点v加入队列 while(!Q.empty()){ int u = Q.front(); //获取队头元素 Q.pop(); //删除队头元素 cout<<V[u].data<<" "; //输出队头元素的值 //遍历该节点的所有邻居节点 EdgeNode* e = V[u].firstEdge; while(e != nullptr){ int w = e->adjvex; //获取邻居节点 if(!visited[w]){ //如果该节点未访问过,则加入队列 visited[w] = true; //标记节点为已访问 Q.push(w); //将邻居节点加入队列 } e = e->next; //继续遍历邻居节点 } } } int main(){ int n, m; cin>>n>>m; //输入顶点数和边数 for(int i=1; i<=n; i++){ cin>>V[i].data; //输入每个节点的数据 V[i].firstEdge = nullptr; //初始化第一条边为空 } for(int i=1; i<=m; i++){ int u, v; cin>>u>>v; //输入一条边的起点和终点 addEdge(u, v); //添加边到邻接表中 } BFS(1); //从第一个节点开始进行广度优先遍历 return 0; } ``` 总的来说,用邻接表存储,并实现广度优先遍历算法的核心就是维护好的邻接表结构,注意访问过的节点,队列的使用等。通过这种方式高效地存储和遍历可以在解决很多实际问题时发挥作用。 ### 回答3: 邻接表是一种存储有向或无向数据结构,它是一个数组列表,其中每个数组元素表示该元素对应顶点的邻接点。在邻接表中,每个顶点v的所有邻接点被存储在一个链表中。因此,当需要遍历时,可以使用广度优先遍历算法,从的某个初始顶点开始,按照广度优先的顺序依次访问所有与该顶点直接相邻的顶点。 邻接表的实现可以使用链表或数组实现。使用邻接链表存储时,每个顶点v对应一个链表,该链表包含v的所有邻接点。当需要访问v的相邻节点时,只需要遍历v对应链表即可。使用邻接数组来存储时,邻接数组的每个元素包含一个布尔值以表示该点是否为邻接点以及存储该点的权值。 实现的广度优先遍历算法的基本思路是:从的某个顶点开始,将该顶点的所有邻接点入队,然后将队首元素出队,继续重复该操作,直到所有顶点均被访问到为止。为了避免重复遍历顶点,需要使用一个布尔类型列表visited,记录每个顶点是否被访问过。 具体实现过程如下: 1. 从起始顶点开始,将该顶点标记为已访问,并将该顶点入队。 2. 从队首取出一个顶点v,遍历该顶点v的所有邻接点w,并将未访问过的邻接点w加入队列,并标记为已访问。 3. 重复以上步骤,直到队列为空。 邻接表存储的优点在于它能够节省存储空间,并且可以快速地查询所有相邻节点。同时,使用邻接表存储的时间复杂度为O(E+V),其中E为边数,V为顶点数。因此,在大多数情况下,使用邻接表存储并基于广度优先遍历算法实现遍历是一种较为高效和实用的方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值