C 图的构建 简单的BFS和DFS 06-图1 列出连通集 (25分)

给定一个有N个顶点和E条边的无向图,请用DFS和BFS分别列出其所有的连通集。假设顶点从0到N−1编号。进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。

输入格式:
输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。随后E行,每行给出一条边的两个端点。每行中的数字之间用1空格分隔。

输出格式:
按照格式,每行输出一个连通集。先输出DFS的结果,再输出BFS的结果。

输入样例:

8 6
0 7
0 1
2 0
4 1
2 4
3 5

输出样例:

{ 0 1 4 2 7 }
{ 3 5 }
{ 6 }
{ 0 1 2 7 4 }
{ 3 5 }
{ 6 }

解题
标准方法建立一个图;
建立BFS,DFS函数遍历图;
主要学习图种各个结构的设置方法;

用数组表示图

1.设置图结构
可以通过 MGraph对象->Nv得到图的顶点数
MGraph对象->Ne得到图的边数

#include<iostream>
#include<queue>
#define MaxVertexNum 11
using namespace std;
typedef int WeightType;
typedef struct GNode *PtrToGNode;
struct GNode
{
	int Nv;     //顶点数 
	int Ne;     //边数
	WeightType G[MaxVertexNum][MaxVertexNum];  //列表存放图 
	//若顶点有数据
	/*
	DataType Data[MaxVertexNum];
	*/ 
};
typedef PtrToGNode MGraph; //以邻接矩阵存储的图类型
//MGraph 为指向图对象的指针

2.造图函数
先得到图种顶点的个数,由此构建一个N个顶点的图,
但不添加边;
返回图的对象指针;

typedef int Vertex;   //用顶点下标表示顶点,为整型
MGraph CreateGraph( int VertexNum)
{
	Vertex V,W;     //v,w为顶点下标 
	MGraph Graph;   //graph为指向图类型的指针;
	
	Graph=new struct GNode;   //新建一个图空间
	Graph->Nv = VertexNum;       //图的顶点数为输入的值 
	Graph->Ne = 0;       //图的边数为0
	
	//图的内容存在G种,编号从0到nv-1 
	for(V=0;V<Graph->Nv;V++)
		for(W=0;W<Graph->Nv;W++)
			Graph->G[V][W] = 0;  //全部置0,表示没有边
	
	return Graph;       //返回指向新建图的指针 
} 

3.边结构
构造边的结构,为后续往图种添加边做准备
Edge对象->V1,得到边左顶点的坐标;
Edge对象->V2,得到边右顶点的坐标;
Weight为边权重,可以读入,也可以自己设置为1;

typedef struct ENode *PtrToENode;   //指向边结构的指针 
struct ENode{      //边结构 
	Vertex V1,V2;   //有向边<V1,V2> 
	WeightType Weight=1;  //边的权重 
};
typedef PtrToENode Edge;       //Edg为指向边的指针

4.插入边函数

//往graph内插入边E 
void InsertEdge(MGraph Graph, Edge E)
{
	Graph->G[E->V1][E->V2] = E->Weight;  //图内数组对应的位置放入权重
	//无向图,则还要放一波
	Graph->G[E->V2][E->V1] = E->Weight;
} 

5.建造图函数
先读入结点数——建造n个结点的无边图;
读入边数——挨个往无边图种插入边;
最后返回插入了边的图;

MGraph BuildGraph()     //创建一个图 
{
	MGraph Graph;  //图类型 Graph 
	Edge E;        //边类型E 
	Vertex V;      //顶点类型 V 
	int Nv, i;
	
	scanf("%d",&Nv); //读入图的大小 
	Graph = CreateGraph(Nv);    //创建一个有Nv个顶点的图
	scanf("%d",&(Graph->Ne)); //读入图的边数
	if(Graph->Ne !=0)   //边数不为0 
	{
		E= new struct ENode;     //E指向新建的边空间
		for(i =0 ; i<Graph->Ne;i++)
		{	
			scanf("%d %d",&E->V1,&E->V2);
			InsertEdge(Graph, E); //往Graph内插入E 
		 } 
	 } 
	 /* 若顶点内有数据,读入数据 
	 for( V=0;V<Graph->Nv;V++)
	 	scanf(" %c",&(Graph->Data[V]));
	 */
	return Graph;
}
//返回所建的图的指针

DFS函数
遇到则输出,输出后深入该行符合要求的新点,直到没有,退回。

int Visited[MaxVertexNum]={0};
void DFS(MGraph A,int a)
{	
	if(Visited[a]) return;
	printf("%d ",a);
	Visited[a]=1;
	for(int i=0;i<A->Nv;i++)
			{
			if(!Visited[i]&&A->G[a][i]==1)
				{
					DFS(A,i);
			}
			}
}

BFS函数
出队即输出,遍历出队的那行,符合要求的入队。

void BFS(MGraph A, int * visited ,int x)
{
	queue<int> T;
	visited[x]=1;
	T.push(x);
	while(!T.empty())
	{
		int f= T.front();
		T.pop();
		printf("%d ",f);
		for(int i=0;i<A->Nv;i++)
		{
			if(!visited[i]&&A->G[f][i]==1)
			{
				T.push(i);
				visited[i]=1;
			}
		}
	}
}

main函数
一行构造图;
后面按要求输出dfs和bfs;

int main()
{
	MGraph t= BuildGraph();

	for(int i=0;i<t->Nv;i++)    //DFS
		if(!Visited[i]){
			printf("{ ");
			DFS(t,i);
			printf("}\n");
		}
	for(int i=0;i<t->Nv;i++) Visited[i]=0;
	for(int i=0;i<t->Nv;i++)    //DFS
		if(!Visited[i]){
			printf("{ ");
			BFS(t,Visited,i);
			printf("}\n");
		}		
}

用链表表示图

1.结点结构
该节点的下标;
该结点的权重;
指向下一个结点的指针;

#include<iostream>
#include<queue>
using namespace std;
typedef int Vertex;         /* 用顶点下标表示顶点,为整型 */
typedef int WeightType;        /* 边的权值设为整型 */
#define MaxVertexNum 11 
//节点结构 
typedef struct AdjVNode *PtrToAdjVNode;   //指向节点的指针 
struct AdjVNode{     //节点 
	Vertex AdjV;   //存放邻接点下标 
	WeightType Weight; //边权重
	PtrToAdjVNode Next;   //指向下一个节点 
}; 

2.链表头结构+图结构
图结构中的邻接表,存放的为链表头指针
写法1:

struct Vnode{
	PtrToAdjVNode FirstEdge;      
	//链表头指针,指向该节点的第一条边 
};
//AdjList是邻接表类型

typedef struct GNode *PtrToGNode;
struct GNode{
	int Nv;     //顶点数 
	int Ne;     //边数 
	Vnode G[MaxVertexNum];  //邻接表       
	//指针数组,对应矩阵每行一个链表,只存非0元素 
};
typedef PtrToGNode LGraph;   //LGraph为指向图的指针 

写法2:
直接把AdjList[]定义为Vnode类型;

typedef struct Vnode{
	PtrToAdjVNode FirstEdge;      //链表头指针,指向该节点的第一条边 
}AdjList[MaxVertexNum];       
//AdjList是邻接表类型

typedef struct GNode *PtrToGNode;
struct GNode{
	int Nv;     //顶点数 
	int Ne;     //边数 
 //邻接表       //指针数组,对应矩阵每行一个链表,只存非0元素 
	AdjList G;
};
typedef PtrToGNode LGraph;   //LGraph为指向图的指针 

3.初始化一个无边图
传入顶点数;
初始化邻接表的结构指针,均为空指针;

//初始化一个无边图
typedef int Vertex; //用顶点下标表示顶点,为int
LGraph CreateGraph(int VertexNum)
{
	Vertex V;
	LGraph Graph;
	
	Graph = new struct GNode; //Graph指向新建的空图
	Graph->Nv = VertexNum;     
	Graph->Ne = 0;         	 
	
	
		for(V=0;V<Graph->Nv;V++)
		Graph->G[V].FirstEdge = NULL; //每一个节点的边指针都指向NULL 
	return Graph;
 } 

4.边结构,与数组表示的完全相同

typedef struct ENode *PtrToENode;   //指向边结构的指针 
struct ENode{      //边结构 
	Vertex V1,V2;   //有向边<V1,V2> 
	WeightType Weight=1;  //边的权重 
};
typedef PtrToENode Edge;       //Edg为指向边的指针

5.插入边的函数
新建邻接点,插入链表表头;
因为后加入的变插入在表头,所以遍历时会先遍历后插入的边;

void InsertEdge(LGraph Graph, Edge E)   //向Graph中插入边E,插在表头 
{
	PtrToAdjVNode NewNode;        //
	
	//为V2建立新的邻接点 
	NewNode = new struct AdjVNode;    
	NewNode->AdjV = E->V2;    //该邻接点的下标 
	NewNode->Weight=E->Weight;  //该邻接点的权重
	//将V2插入V1的表头
	NewNode->Next = Graph->G[E->V1].FirstEdge; 
	Graph->G[E->V1].FirstEdge = NewNode;
	
	NewNode = new struct AdjVNode;    
	NewNode->AdjV = E->V1;    //该邻接点的下标 
	NewNode->Weight=E->Weight;  //该邻接点的权重
	//将V1插入V2的表头
	NewNode->Next = Graph->G[E->V2].FirstEdge; 
	Graph->G[E->V2].FirstEdge = NewNode;
}

6.构建图函数,与数组表示的构建图函数相同

LGraph BuildGraph()
{
    LGraph Graph;
    Edge E;
    Vertex V;
    int Nv, i;
     
    scanf("%d", &Nv);   /* 读入顶点个数 */
    Graph = CreateGraph(Nv); /* 初始化有Nv个顶点但没有边的图 */ 
    
	scanf("%d", &(Graph->Ne));   /* 读入边数 */
    if ( Graph->Ne != 0 ) { /* 如果有边 */ 
        E = new struct ENode; /* 建立边结点 */ 
		/* 读入边,格式为"起点 终点 权重",插入邻接矩阵 */
        for (i=0; i<Graph->Ne; i++) {
            scanf("%d %d", &E->V1, &E->V2); 
            /* 注意:如果权重不是整型,Weight的读入格式要改 */
            InsertEdge( Graph, E );
        }
    } 
    /* 如果顶点有数据的话,读入数据 */
    return Graph;
}

DFS和BFS
遍历链表的方法:
构建链表指针W,for(W=A->G[a].FirstEdge;W;W=W->Next)
从表头指向的第一个元素开始,当链表还有next时,指向next;
即可完成链表遍历;

int Visited[MaxVertexNum]={0};
void DFS(LGraph A,int a)
{	
	PtrToAdjVNode W;
	printf("%d ",a);
	Visited[a]=1;
	
	for(W=A->G[a].FirstEdge;W;W=W->Next)  //对a的每一条边遍历
		if(!Visited[W->AdjV])  	//若该值未被访问过
			DFS(A,W->AdjV); 	
}

void BFS(LGraph A,int a) 
{
	queue<int> f;
	f.push(a);
	Visited[a]=1;
	
	while(!f.empty())
	{
	int F= f.front();
	f.pop();
	printf("%d ",F);
	PtrToAdjVNode W;     //保存当前链表
	for(W=A->G[F].FirstEdge;W;W=W->Next)
		if(!Visited[W->AdjV]){
			f.push(W->AdjV);
			Visited[W->AdjV]=1;
		}	 
}
}

main函数也与数组方法的相同

int main()
{
	LGraph t=BuildGraph();
		
	for(int i=0;i<t->Nv;i++)    //DFS
		if(!Visited[i]){
			printf("{ ");
			DFS(t,i);
			printf("}\n");
		}
	for(int i=0;i<t->Nv;i++) Visited[i]=0;
	for(int i=0;i<t->Nv;i++)    //DFS
		if(!Visited[i]){
			printf("{ ");
			BFS(t,i);
			printf("}\n");
		}
/*  输出为 
{ 0 2 4 1 7 }   因为后入的在表头,所以2后面连接的2反而在0的next的前面 
{ 3 5 }
{ 6 }
*/
}


链表与数组比较
链表插入边的顺序是后插入的在表头,故遍历时会先遍历到后插入的数据,故此题输出会不正确;
数组会按顺序遍历;
链表适合分散图,可扩充性强,点越散越深空间;
数组更方便;

这样的写法虽然麻烦,但是要链表和数组方法的main函数都相同
进行合理的函数构造,可以增加代码复用性;
快速代码:

#include<iostream>
using namespace std;
#include<queue>

int N,E;
int Graph[11][11]={0};
int visited[11]={0};

void DFS(int a){
	visited[a]=1; 
	cout<<a<<" ";
	for(int i=0;i<N;i++)       //找与a有边的数 
 	{
		if(!visited[i]&&Graph[a][i]==1)        
		//有边且未遍历——标记后继续找 
			DFS(i);
	}
}
queue <int> result;
void BFS(int a){
	result.push(a);
	visited[a]=1;
	while(!result.empty())
	{
		int t=result.front();
		result.pop();
		cout<<t<<" "; 
		for(int i=0;i<N;i++)
		{
			if(!visited[i]&&Graph[t][i]==1){
			result.push(i);
			visited[i]=1;
		}
		}	
	 } 	
}

int main()
{
	int a,b;
	cin>>N>>E;
	for(int i=0;i<E;i++)
	{
		cin>>a>>b;
		Graph[a][b]=1;
		Graph[b][a]=1;
	}
	for (int j=0;j<N;j++)
	{
		if(!visited[j]){
			cout<<"{ ";
			DFS(j);
			cout<<"}"<<endl;
		}
	}
	for(int i=0;i<N;i++) visited[i]=0;
	for(int j=0;j<N;j++)
	{
		if(!visited[j]){
			cout<<"{ ";
			BFS(j);
			cout<<"}"<<endl;
		}
	}
 } 
 
 //列表表示图法
发布了77 篇原创文章 · 获赞 3 · 访问量 3023

猜你喜欢

转载自blog.csdn.net/BLUEsang/article/details/105398477
今日推荐