给定一个有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;
}
}
}
//列表表示图法