数据结构—图的广度优先搜索

本文详细介绍了广度优先搜索(BFS)算法,包括其基本思想和在二叉树层序遍历中的应用。通过邻接矩阵和邻接表两种数据结构,给出了C++代码实现。此外,还分析了BFS的空间和时间复杂度,并展示了无向图的邻接矩阵和邻接表的存储方式。最后,提供了完整的BFS遍历程序及其运行结果。
摘要由CSDN通过智能技术生成

广度优先搜索

在这里插入图片描述

  • 广度优先搜索(Breadth- First- Search,BFS)类似于二叉树的层序遍历算法。
  • 基本思想是:首先访问起始顶点ν,接着由ν出发,依次访问v的各个未访问过的邻接顶点 w 1 , w 2 , . . . , w i w_1,w_2,...,w_i w1,w2,...,wi,然后依次访问 w 1 , w 2 , . . . , w i w_1,w_2,...,w_i w1,w2,...,wi的所有未被访问过的邻接顶点;再从这些访问过的顶点出发,访问它们所有未被访问过的邻接顶点,直至图中所有顶点都被访问过为止。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作为始点,重复上述过程,直至图中所有顶点都被访问到为止。
    在这里插入图片描述

代码实现(邻接矩阵法)

#include<stdio.h>
#include<malloc.h>
#define MaxVertenNum 100 //顶点数目的最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //存放顶点信息
typedef struct
{
	VertexType Vex[MaxVertenNum]; //顶点表
	EdgeType Edge[MaxVertenNum][MaxVertenNum]; //邻接矩阵
	int vexnum, edgenum; //图的当前顶点数和边数
}MGraph;

typedef struct
{
	VertexType data[MaxVertenNum];
	int front, rear;
}SqQueue;

void InitQueue(SqQueue*& qu) //初始化队列
{
	qu = (SqQueue*)malloc(sizeof(SqQueue));
	qu->rear = qu->front = 0;
}

bool EmptyQueue(SqQueue* qu) //判断队列是否为空
{
	return qu->rear == qu->front;
}

bool enQueue(SqQueue*& qu, int& x) //入队列
{
	if ((qu->rear + 1) % MaxVertenNum == qu->front) return false;
	qu->data[qu->rear] = x;
	qu->rear = (qu->rear + 1) % MaxVertenNum;
}

bool deQueue(SqQueue*& qu, int& x) //出队列
{
	if (qu->rear == qu->front) return false;
	x = qu->data[qu->front];
	qu->front = (qu->front + 1) % MaxVertenNum;
}

void DestroyQueue(SqQueue*& qu) //销毁队列
{
	free(qu);
}

void InitG(MGraph& g) //初始化
{
	int i, j;
	for (i = 0; i < MaxVertenNum; i++) g.Vex[i] = '\0';
	for (i = 0; i < MaxVertenNum; i++)
		for (j = 0; j < MaxVertenNum; j++)
			g.Edge[i][j] = 0;
	g.vexnum = 0;
	g.edgenum = 0;
}

void CreateVex(MGraph& g) //创建顶点信息
{
	int i = 0, count = 0;
	printf("输入图的顶点:");
	VertexType ch = getchar();
	while (ch != '#') //#代表结束输入
	{
		g.Vex[i++] = ch;
		count++; //统计顶点个数
		ch = getchar();
	}
	g.vexnum = count;
}

void CreateEdge(MGraph& g) //创建邻接矩阵信息
{
	EdgeType b;
	int i, j = 0;
	printf("输入邻接矩阵信息:\n");
	for (i = 0; i < g.vexnum; i++)
		for (j = 0; j < g.vexnum; j++)
		{
			scanf("%d", &b);
			g.Edge[i][j] = b;
		}
}

void Info(MGraph& g) //图的顶点数和边数
{
	int count = 0;
	for (int i = 0; i < g.vexnum; i++)
		for (int j = 0; j < g.vexnum; j++)
			if (g.Edge[i][j] == 1) count++;
	g.edgenum = count / 2;
}

int CountDegree(MGraph g, VertexType point) //统计每个顶点的度
{
	int j, k, count = 0;
	for (int i = 0; i < g.vexnum; i++)
		if (g.Vex[i] == point) j = i;
	for (int k = 0; k < g.vexnum; k++) if (g.Edge[j][k] == 1) count++;
	return count;
}

void PrintG(MGraph g) //输出各顶点的连接情况
{
	for (int i = 0; i < g.vexnum; i++)
	{
		for (int j = 0; j < g.vexnum; j++)
			if (g.Edge[i][j] == 1) printf("%c->%c		", g.Vex[i], g.Vex[j]);
		printf("\n");
	}
}

int FirstNeighbor(MGraph g, VertexType x) 
//求图G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回-1。
{
	int i, j;
	for (i = 0; i < g.vexnum; i++)
		if (x == g.Vex[i]) j = i;
	if (j >= g.vexnum) return -1; //图中不存在值为x的顶点
	else
	{
		for (i = 0; i < g.vexnum; i++)
			if (g.Edge[j][i] == 1) return i; //返回顶点号
		if (i >= g.vexnum) return -1; //x没有邻接点
	}
}

int NextNeighbor(MGraph g, VertexType x, VertexType y)
//返回除y外顶点x的下一个邻接点的顶点号,若y是x的最后一 个邻接点,则返回-1
{
	int i, j, k;
	for (i = 0; i < g.vexnum; i++)
	{
		if (g.Vex[i] == x) j = i;
		if (g.Vex[i] == y) k = i;
	}
	if (j >= g.vexnum || k >= g.vexnum) return false; //不存在顶点x或顶点y
	else
	{
		for (i = k + 1; i < g.vexnum; i++)
			if (g.Edge[j][i] == 1) return i; //返回顶点号
		if (i >= g.vexnum) return -1; //x没有邻接点
	}
}

void PrintMatrix(MGraph g) //输出邻接矩阵
{
	int i, j;
	printf("输出邻接矩阵:\n");
	for (i = 0; i < g.vexnum; i++)
		printf("\t%c", g.Vex[i]);
	printf("\n");

	for (i = 0; i < g.vexnum; i++)
	{
		printf("%c", g.Vex[i]);
		for (j = 0; j < g.vexnum; j++)
			printf("\t%d", g.Edge[i][j]);
		printf("\n");
	}
	printf("\n");
}

bool visited[MaxVertenNum]; //访问标记数组
void BFS(MGraph g, VertexType ch) //从顶点出发,广度优先遍历图
{
	int i, j, k;
	for (i = 0; i < g.vexnum; i++)
		if (g.Vex[i] == ch) j = i;
	SqQueue* qu;
	InitQueue(qu); //初始化队列
	printf("%c ", ch);
	visited[j] = true; //做已访问标记
	enQueue(qu, j); //顶点入队列
	while (!EmptyQueue(qu))
	{
		deQueue(qu, k); //顶点出队列
		for (i = FirstNeighbor(g, g.Vex[k]); i >= 0; i = NextNeighbor(g, g.Vex[k], g.Vex[i])) //检测所有邻接点
		{
			if (!visited[i]) //尚未访问的邻接点
			{
				printf("%c ", g.Vex[i]);
				visited[i] = true; //做已访问标记
				enQueue(qu, i); //顶点入队列
			}
		}
	}
}

void BFSTraverse(MGraph g) //对图G进行广度优先遍历
{
	int i;
	for (i = 0; i < g.vexnum; i++) visited[i] = false; //访问标记数组初始化
	for (i = 0; i < g.vexnum; i++) //从0号顶点开始遍历
		if (!visited[i]) BFS(g, g.Vex[i]); //对每个连通分量调用一次BFS
}

int main()
{
	MGraph g;
	VertexType Vex[MaxVertenNum];
	EdgeType Edge[MaxVertenNum][MaxVertenNum];
	InitG(g);
	CreateVex(g);
	CreateEdge(g);

	Info(g);
	printf("\n无向图G中共有:%d个顶点,%d条边\n", g.vexnum, g.edgenum);

	PrintMatrix(g);

	printf("输出无向图G中各顶点的连接情况:\n");
	PrintG(g);
	printf("\n");

	int sumdegree = 0, i;
	for (i = 0; i < g.vexnum; i++)
	{
		int degree;
		degree = CountDegree(g, g.Vex[i]);
		printf("顶点%c的度为:%d\n", g.Vex[i], degree);
		sumdegree = sumdegree + degree;
	}
	printf("无向图G中所有顶点的度之和为:%d\n", sumdegree);

	printf("\n输出广度优先搜索结果:");
	BFSTraverse(g);

	return 0;
}

运行结果

在这里插入图片描述

程序分析

  • 无论是邻接表还是邻接矩阵的存储方式,BFS算法都需要借助一个辅助队列Q,n个顶点均需入队一次,在最坏的情况下,空间复杂度为O(|V|)
  • 采用邻接矩阵存储方式时:访问|V|个顶点需要O(|V|)的时间,查找每个顶点的邻接点都需要O(|V|)的时间,而总共有|V|个顶点,即时间复杂度=O(|V| 2 ^2 2)

代码实现(邻接表法)

#include<stdio.h>
#include<malloc.h>
#define MaxVertexNum 100
#define NodeNum 7
typedef char VertexType;

//图的类型声明
typedef struct ArcNode
{
	int adjvex; //该边的邻接点编号
	struct ArcNode* nextarc; //指向下条边的指针
	//int weight; //该边的相关信息,如权值
}ArcNode; //边结点的类型

typedef struct VNode
{
	VertexType data; //顶点信息
	ArcNode* firstarc; //指向第一个边结点
}VNode;

typedef struct
{
	VNode adjlist[MaxVertexNum]; //邻接表的头结点数组
	int vexnum, edgenum; //图中的顶点数和边数
}AdjGraph; //完整的图邻接表类型

//队列的类型声明
typedef struct
{
	VertexType data[MaxVertexNum];
	int front, rear;
}SqQueue;

void InitQueue(SqQueue*& qu)
{
	qu = (SqQueue*)malloc(sizeof(SqQueue));
	qu->rear = qu->front = 0;
}

bool EmptyQueue(SqQueue* qu)
{
	return qu->rear == qu->front;
}

bool enQueue(SqQueue*& qu, int& x)
{
	if ((qu->rear + 1) % MaxVertexNum == qu->front) return false;
	qu->data[qu->rear] = x;
	qu->rear = (qu->rear + 1) % MaxVertexNum;
}

bool deQueue(SqQueue*& qu, int& x)
{
	if (qu->rear == qu->front) return false;
	x = qu->data[qu->front];
	qu->front = (qu->front + 1) % MaxVertexNum;
}

void DestroyQueue(SqQueue*& qu)
{
	free(qu);
}

void InitG(AdjGraph& g)
{
	for (int i = 0; i < MaxVertexNum; i++)
	{
		g.adjlist[i].data = '\0';
		g.adjlist[i].firstarc = NULL;
	}
	g.vexnum = 0;
	g.edgenum = 0;
}

void CreateVNde(AdjGraph& g)
{
	int i = 0, count = 0;
	printf("输入图的顶点:");
	VertexType ch = getchar();
	while (ch != '#')
	{
		g.adjlist[i].data = ch;
		g.adjlist[i].firstarc = NULL;
		i++;
		count++; //统计顶点个数
		ch = getchar();
	}
	g.vexnum = count;
}

void CreateANode(AdjGraph& g, VertexType ch, int num)
{
	ArcNode* p, * r = g.adjlist[0].firstarc;
	int i, j, k;
	while (num--)
	{
		p = (ArcNode*)malloc(sizeof(ArcNode));
		p->nextarc = NULL;
		printf("输入顶点的编号:");
		scanf("%d", &i);
		for (j = 0; j < g.vexnum; j++)
			if (g.adjlist[j].data == ch) k = j;
		if (i != k)
		{
			p->adjvex = i;
			if (g.adjlist[k].firstarc == NULL)
			{
				g.adjlist[k].firstarc = p;
				r = p;
			}
			else
			{
				r->nextarc = p;
				r = p;
			}
		}
	}
}

int FirstNeighbor(AdjGraph& g, VertexType x)
//求图G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回 - 1
{
	int i, j;
	for (i = 0; i < g.vexnum; i++)
		if (g.adjlist[i].data == x) j = i;
	if (j >= g.vexnum) return -1; //图中不存在x
	ArcNode* p = g.adjlist[j].firstarc;
	if (p == NULL) return -1; //若x没有邻接点
	else return p->adjvex; //返回顶点号
}

int NextNeighbor(AdjGraph& g, VertexType x, VertexType y)
//求图G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回 - 1
{
	int i, j, k;
	for (i = 0; i < g.vexnum; i++)
	{
		if (g.adjlist[i].data == x) j = i;
		if (g.adjlist[i].data == y) k = i;
	}
	if (j >= g.vexnum || k >= g.vexnum) return -1; //不存在顶点x或顶点y
	else
	{
		ArcNode* p = g.adjlist[j].firstarc;
		while (p != NULL)
		{
			if (p->adjvex == k)
			{
				if (p->nextarc != NULL) return p->nextarc->adjvex;
				else return -1;
			}
			else p = p->nextarc;
		}
	}
}

void CountDegree(AdjGraph& g)
{
	ArcNode* p;
	int sumdegree = 0;
	printf("输出各顶点的度:\n");
	for (int i = 0; i < g.vexnum; i++)
	{
		p = g.adjlist[i].firstarc;
		int degree = 0;
		while (p != NULL)
		{
			degree++;
			p = p->nextarc;
		}
		sumdegree += degree;
		printf("顶点%c的度为:%d\n", g.adjlist[i].data, degree);
	}
	printf("无向图G中所有顶点的度之和为:%d", sumdegree);
	g.edgenum = sumdegree / 2;
}

void PrintG(AdjGraph g)
{
	int i;
	ArcNode* p;
	printf("输出各顶点的连接情况:\n");
	for (i = 0; i < g.vexnum; i++)
	{
		p = g.adjlist[i].firstarc;
		printf("%d(%c) ", i, g.adjlist[i].data);
		while (p != NULL)
		{
			printf("-%d(%c) ", p->adjvex, g.adjlist[p->adjvex].data);
			p = p->nextarc;
		}
		printf("\n");
	}
	printf("\n");
}


bool visited[MaxVertexNum]; //访问标记数组
void BFS(AdjGraph g, VertexType ch) //从顶点出发,广度优先遍历图G
{
	printf("%c ", ch); //访问初始顶点
	int i, j, k;
	for (i = 0; i < g.vexnum; i++)
		if (g.adjlist[i].data == ch) j = i;
	visited[j] = true; //做已访问标记
	SqQueue* qu;
	InitQueue(qu); //初始化队列
	enQueue(qu, j); //顶点入队列
	while (!EmptyQueue(qu))
	{
		deQueue(qu, k); //顶点出队列
		for (i = FirstNeighbor(g, g.adjlist[k].data); i >= 0; i = NextNeighbor(g, g.adjlist[k].data, g.adjlist[i].data)) //检测所有邻接点
			if (!visited[i]) //尚未访问的邻接点
			{
				printf("%c ", g.adjlist[i].data); //访问顶点
				visited[i] = true; //对顶点做已访问标记
				enQueue(qu, i); //顶点入队列
			}
	}
}

void BFSTraverse(AdjGraph g) //对图G进行广度优先遍历
{
	int i;
	for (i = 0; i < g.vexnum; i++)
		visited[i] = false; //访问标记数组初始化
	for (i = 0; i < g.vexnum; i++) //从0号顶点开始遍历
		if (!visited[i]) BFS(g, g.adjlist[i].data); //对每个连通分量调用一次BFS
		//vi为访问过,从vi开始BFS
}

int main()
{
	AdjGraph g;
	VertexType ch;
	int i, num;
	ArcNode* p;

	InitG(g);
	CreateVNde(g);
	printf("\n");
	for (i = 0; i < g.vexnum; i++)
	{
		printf("创建顶点%c的边结点\n", g.adjlist[i].data);
		printf("输入要创建的边结点的个数:");
		scanf("%d", &num);
		CreateANode(g, g.adjlist[i].data, num);
		printf("\n");
	}

	PrintG(g);

	CountDegree(g);
	printf("\n");
	printf("\n无向图G的顶点数为:%d,边数为:%d\n", g.vexnum, g.edgenum);

	printf("\n输出广度优先搜索结果:");
	BFSTraverse(g);

	return 0;
}

运行结果

在这里插入图片描述
在这里插入图片描述

程序分析

  • 无论是邻接表还是邻接矩阵的存储方式,BFS算法都需要借助一个辅助队列Q,n个顶点均需入队一次,在最坏的情况下,空间复杂度为O(|V|)
  • 采用邻接表存储方式时:访问|V|个顶点需要O(|V|)的时间,查找各个顶点的邻接点共需要O(|E|)的时间,时间复杂度=O(|V|+|E|)
  • 采用邻接矩阵存储方式时,查找每个顶点的邻接点所需的时间为O(|V|),故总的时间复杂度为O(|V|²)

结论:对于无向图,调用BFS函数的次数=连通分量数。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值