广度优先搜索
- 广度优先搜索(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函数的次数=连通分量数。