当前位置:   article > 正文

图(graph)的遍历-----广度优先遍历(BFS)

广度优先遍历

目录

前言

广度优先遍历(BFS)

1.基本概念 

2.算法过程 

图的广度优先遍历

1.邻接矩阵

2.邻接表

3.算法比较


前言

        上一期学习了图的深度优先遍历,(深度优先遍历:图(graph)的遍历----深度优先(DFS)遍历-CSDN博客)那这一期我们接着学习图的广度优先遍历(BFS),对于图的广度优先遍历我们也不陌生,在二叉树的层序遍历也就是广度优先遍历的一种了。那对于图的广度优先遍历有有什么不同呢?我们接着往下看。

二叉树的层序遍历(广度优先遍历)

广度优先遍历(BFS

1.基本概念 

        广度优先搜索(Breadth First Search)简称广搜或者 BFS,是遍历存储结构的一种算法,既适用于无向图(网),也适用于有向图(网)。

        广度优先遍历是一种图和树的遍历策略,它的核心思想是从一个起始节点开始,访问其所有邻近节点,然后再按照相同的方式访问这些邻近节点的邻近节点。这种遍历方式类似于波纹在水面上扩散的过程。

在广度优先遍历中,我们通常使用一个队列(Queue)来存储待访问的节点。初始时,将起始节点放入队列。然后,执行以下操作直到队列为空:

  1. 从队列中取出一个节点。
  2. 访问该节点,并将其标记为已访问。
  3. 将该节点的所有未被访问的邻近节点加入队列。

2.算法过程 

 下面给大家看一个示例就知道了。

 对于上图这么一个图,从顶点V1出发开始进行广度优先遍历,其过程如下:

开始访问V1,然后V1入队进行访问操作。

接着就是访问与顶点V1连接的顶点V2和V3,依次入队。

 然后就是访问与V3相连的顶点V4,V5依次入队访问。

再然后就是与顶点V3相连的顶点V6,V7,执行同样的操作。 

 最后就是V8顶点进行访问

 整体结果如下:

看了上面这几个图,我相信大家都理解了广度优先遍历的次序了吧,无非就是一层一层去访问而已。前面我们说过图的两种存储方式,分别是邻接矩阵和邻接表,那下面我们就去学习邻接矩阵和邻接表的广度优先遍历算法,看看同一个图不同的两种存储方式的遍历结果会有那些不同呢?

图的广度优先遍历

广度优先遍历 (Breadth-First-Search,BFS) 要点:

  1. 找到与一个顶点相邻的所有顶点
  2. 标记哪些顶点被访问过,需要一个辅助数组
  3. 需要一个辅助队列

辅助数组visited作用:

是用来标记访问过的节点,初始化全为0,表示都没有访问过,每次访问了一个节点,下标对应的辅助数组的位置设置为1表示已经访问,下次访问之前先去通过visited数组判断这个节点是否访问过,如果访问过就跳过这个节点,反之就进行访问操作。

辅助队列的作用:

用于储存当前访问节点所连接的节点,然后进行入队操作,要去访问的时候就进行出队的操作。

辅助队列代码如下:

头文件(queue.h)代码:

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<assert.h>
  5. #include<string.h>
  6. #include <stdbool.h>
  7. //数据类型
  8. typedef struct datatype {
  9. char id[10];
  10. //……
  11. }
  12. ElemType;
  13. //定义节点
  14. typedef struct node {
  15. ElemType data;
  16. struct node* next;
  17. }Node;
  18. //定义队列
  19. typedef struct queue {
  20. int count; //计数
  21. Node* front;//指向队头指针
  22. Node* rear;//指向队尾指针
  23. }Queue;
  24. void Queue_init(Queue* queue);//初始化
  25. bool isEmpty(Queue* queue);//判空
  26. void enQueue(Queue* queue, ElemType data);//入队
  27. Node* deQueue(Queue* queue);//出队
  28. ElemType head_data(Queue queue);//获取队头数据

queue.c代码: 

  1. #include"queue.h"
  2. //初始化
  3. void Queue_init(Queue* queue) {
  4. assert(queue);
  5. queue->front = NULL;
  6. queue->rear = NULL;
  7. queue->count=0;
  8. }
  9. //创建节点
  10. Node* create_node(ElemType data) {
  11. Node* new_node = (Node*)malloc(sizeof(Node));
  12. if (new_node) {
  13. new_node->data = data;
  14. new_node->next = NULL;
  15. return new_node;
  16. }
  17. else
  18. {
  19. printf("ERRPR\n");
  20. }
  21. }
  22. //判断是否空队列
  23. bool isEmpty(Queue* queue) {
  24. assert(queue);
  25. if (queue->count == 0)
  26. {
  27. return true;
  28. }
  29. return false;
  30. }
  31. //入队
  32. void enQueue(Queue* queue, ElemType data) {
  33. assert(queue);
  34. Node* new_node = create_node(data);
  35. if (queue->rear == NULL) {
  36. queue->front = new_node;
  37. queue->rear = new_node;
  38. queue->count++;
  39. }
  40. else
  41. {
  42. queue->rear->next = new_node;
  43. queue->rear = new_node;
  44. queue->count++;
  45. }
  46. }
  47. //出队
  48. Node* deQueue(Queue* queue) {
  49. assert(queue);
  50. if (!isEmpty(queue)) {
  51. Node* deNode = queue->front;
  52. queue->front = deNode->next;
  53. queue->count--;
  54. return deNode;
  55. }
  56. printf("error\n");
  57. return NULL;
  58. }
  59. //获取队头数据
  60. ElemType head_data(Queue queue) {
  61. return queue.front->data;
  62. }

1.邻接矩阵

广度优先遍历代码如下:

  1. //01--邻接矩阵
  2. #include"queue.h"//导入头文件
  3. #define Maxint 32767
  4. #define Maxnum 100//最大顶点数
  5. ElemType;
  6. //图的邻接数组
  7. typedef struct graph {
  8. ElemType vexs[Maxnum];//图数据
  9. int matrix[Maxnum][Maxnum];//二维数组矩阵
  10. int vexnum;//点数
  11. int arcnum;//边数
  12. }Graph;
  13. //节点id查找下标
  14. int Locate_vex(Graph G, char* id) {
  15. for (int i = 0; i < G.vexnum; i++)
  16. if (strcmp(G.vexs[i].id,id)==0)
  17. return i;
  18. return -1;
  19. }
  20. //节点id查找这个数据体节点
  21. ElemType Locate_data(Graph G, char* id) {
  22. for (int i = 0; i < G.vexnum; i++)
  23. if (strcmp(G.vexs[i].id, id) == 0)
  24. return G.vexs[i];
  25. }
  26. //构造邻接矩阵(无向图,对称矩阵)(有向图)赋权图
  27. void Create_graph(Graph* G) {
  28. printf("请输入顶点个数和边的个数:\n");
  29. scanf("%d %d", &G->vexnum, &G->arcnum);//输入点数边数
  30. printf("请输入顶点数据:\n");
  31. for (int i = 0; i < G->vexnum; i++) {
  32. scanf("%s", G->vexs[i].id);
  33. }
  34. for (int x = 0; x < G->vexnum; x++) {
  35. for (int y = 0; y < G->vexnum; y++) {
  36. if (x == y)
  37. G->matrix[x][y] = 0;//对角线初始化为0
  38. else
  39. G->matrix[x][y] = Maxint;//其他初始化为Maxint
  40. }
  41. }
  42. printf("请输入边相关数据:\n");
  43. for (int k = 0; k < G->arcnum; k++) {
  44. char a[10], b[10];
  45. int w;
  46. scanf("%s %s %d", a, b, &w);
  47. //a->b
  48. int i = Locate_vex(*G, a);
  49. int j = Locate_vex(*G, b);
  50. //矩阵赋值
  51. G->matrix[i][j] = w;
  52. G->matrix[j][i] = w;//删掉这个,表示有向图
  53. }
  54. }
  55. //输出矩阵
  56. void print_matrix(Graph G) {
  57. printf("矩阵为:\n");
  58. for (int i = 0; i < G.vexnum; i++) {
  59. for (int j = 0; j < G.vexnum; j++) {
  60. if (G.matrix[i][j] == Maxint)
  61. printf("∞\t");
  62. else
  63. printf("%d\t", G.matrix[i][j]);
  64. }
  65. printf("\n");
  66. }
  67. printf("图的顶点个数和边数:%d,%d\n", G.vexnum, G.arcnum);
  68. }
  69. //访问输出
  70. void visit(Graph G,int loca) {
  71. printf("%s ", G.vexs[loca].id);
  72. }
  73. //广度优先遍历BFS
  74. void BFS(Graph G, char* id) {
  75. //辅助数组,标记是否访问过
  76. int* visited = (int*)malloc(sizeof(int) * G.vexnum);
  77. memset(visited, 0, sizeof(int) * G.vexnum);//初始化为0表示未访问过
  78. ElemType begin = Locate_data(G, id);//通过输入的id找到这个数据节点
  79. Queue q;//定义队列
  80. Queue_init(&q);//初始化队列
  81. enQueue(&q, begin);//把第一个节点进行入队操作
  82. visited[Locate_vex(G,id)] = 1;//visited对应的位置标记为1,表示这个节点已经入队,将进行访问操作
  83. while (!isEmpty(&q)) {//进入到循环
  84. int location = Locate_vex(G, head_data(q).id);//找到队头节点在图中的位置(下标)
  85. //把当前队头节点相连的节点依次入队操作
  86. for (int i = 0; i < G.vexnum; i++) {
  87. if (G.matrix[location][i] != 0 && !visited[i]) {
  88. enQueue(&q, G.vexs[i]);
  89. visited[i] = 1;//入队后标记为1,
  90. }
  91. }
  92. Node*v = deQueue(&q);//进行出队操作
  93. location = Locate_vex(G, v->data.id);
  94. visit(G, location);//访问
  95. }
  96. free(visited);
  97. visited = NULL;
  98. }

main函数,测试代码:

  1. int main() {
  2. Graph G;
  3. Create_graph(&G);
  4. print_matrix(G);
  5. printf("\nBFS:\n");
  6. BFS(G, "B");
  7. }

输入的图结构如图所示: 

2.邻接表

代码如下:

  1. //02--邻接表
  2. #include"queue.h"
  3. //边节点存储结构
  4. typedef struct arcnode {
  5. int index;//指向顶点的位置
  6. int weight;//权
  7. struct arcnode* nextarc;//指向下一个边节点
  8. }Anode;
  9. //顶点结点存储结构
  10. typedef struct vexnode {
  11. ElemType data;
  12. Anode* firstarc;
  13. }Vhead;
  14. //图结构
  15. typedef struct {
  16. Vhead* vertices;
  17. int vexnum;
  18. int arcnum;
  19. }Graph;
  20. //顶点id查找下标
  21. int Locate_vex(Graph G, char* id) {
  22. for (int i = 0; i < G.vexnum; i++)
  23. if (strcmp(G.vertices[i].data.id,id)==0)
  24. return i;
  25. return -1;
  26. }
  27. //顶点编号查找整个数据
  28. ElemType Locate_data(Graph G, char* id) {
  29. int index;
  30. for (int i = 0; i < G.vexnum; i++) {
  31. if (strcmp(G.vertices[i].data.id, id) == 0) {
  32. index = i;
  33. break;
  34. }
  35. }
  36. return G.vertices[index].data;
  37. }
  38. //创建头节点
  39. void Create_vexhead(Graph *G,int n) {
  40. G->vertices = (Vhead*)malloc(sizeof(Vhead) *n);
  41. if (!G->vertices) {
  42. printf("ERROR\n");
  43. exit(-1);
  44. }
  45. else {
  46. for (int i = 0; i < n ; i++) {
  47. scanf("%s", G->vertices[i].data.id);
  48. G->vertices[i].firstarc = NULL;
  49. }
  50. }
  51. }
  52. //创建一个边节点
  53. Anode* Create_arcnode(int loca, int w) {
  54. Anode* arc = (Anode*)malloc(sizeof(Anode));
  55. if (!arc)
  56. {
  57. printf("ERROR\n");
  58. exit(-1);
  59. }
  60. arc->index = loca;
  61. arc->nextarc = NULL;
  62. arc->weight = w;
  63. return arc;
  64. }
  65. //创建邻接表(无向图)(有向图)
  66. void Create_graph(Graph* G) {
  67. printf("输入顶点数和边数:\n");
  68. scanf("%d %d", &G->vexnum, &G->arcnum);
  69. printf("输入顶点数据:\n");
  70. Create_vexhead(G, G->vexnum);
  71. printf("输入边数据:\n");
  72. for (int k = 0; k <G->arcnum; k++) {
  73. ElemType a, b;
  74. int w;
  75. scanf("%s%s%d", a.id, b.id, &w);
  76. int i = Locate_vex(*G, a.id);
  77. int j = Locate_vex(*G, b.id);
  78. //头插法
  79. //a->b
  80. Anode* p = Create_arcnode(j, w);
  81. p->nextarc = G->vertices[i].firstarc;
  82. G->vertices[i].firstarc = p;
  83. //如果创建有向图的话,直接把下面的代码删掉即可
  84. //b->a
  85. Anode* q = Create_arcnode(i, w);
  86. q->nextarc = G->vertices[j].firstarc;
  87. G->vertices[j].firstarc = q;
  88. }
  89. }
  90. //访问
  91. void visit(Graph G, int index) {
  92. printf("%s ", G.vertices[index].data.id);
  93. }
  94. //输出图
  95. void print(Graph G) {
  96. printf("以下是图的顶点连接关系:\n");
  97. for (int i = 0; i < G.vexnum; i++) {
  98. printf("%s:", G.vertices[i].data.id);
  99. Anode* cur= G.vertices[i].firstarc;
  100. while (cur) {
  101. visit(G, cur->index);
  102. cur = cur->nextarc;
  103. }
  104. printf("\n");
  105. }
  106. printf("顶点和边数分别是:%d %d\n", G.vexnum, G.arcnum);
  107. }
  108. //广度优先遍历BFS
  109. void BFS(Graph G, char* begin_id) {
  110. //visited 标记是否访问
  111. int* visited = (int*)malloc(sizeof(int) * G.vexnum);
  112. memset(visited, 0, sizeof(int) * G.vexnum);//初始化0
  113. ElemType begin = Locate_data(G, begin_id);
  114. //初始化队列
  115. Queue qu;
  116. Queue_init(&qu);
  117. //起点入队
  118. enQueue(&qu, begin);
  119. visited[Locate_vex(G, begin_id)] = 1;
  120. while (!isEmpty(&qu)) {
  121. int index=Locate_vex(G,head_data(qu).id);//获取当前队头元素的图位置
  122. Anode* cur = G.vertices[index].firstarc;//获取队头连接的顶点位置
  123. while (cur) {
  124. if (visited[cur->index] == 0) {
  125. //如果是未访问过的话,就进行入队操作
  126. enQueue(&qu, G.vertices[cur->index].data);
  127. visited[cur->index] = 1;
  128. }
  129. cur = cur->nextarc;
  130. }
  131. //出队列,遍历
  132. Node* p = deQueue(&qu);
  133. visit(G, Locate_vex(G, p->data.id));
  134. }
  135. free(visited);
  136. visited = NULL;
  137. }
  138. int main() {
  139. Graph G;
  140. Create_graph(&G);
  141. print(G);
  142. printf("广度优先遍历结果:\n");
  143. BFS(G, "A");
  144. }

测试结果:

3.算法比较

时间复杂度

邻接矩阵

如果使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循吓检测矩阵中的整整一行( n个元素),总的时间代价为O(n^2)。

邻接表

用邻接表来表示图,虽然有2e个表结点,但只需扫描e个结点即可完成遍历,加上访问n个头结点的时间,时间复杂度为O(n+e)。

空间复杂度

空间复杂度相同,都是O(n)(借用了堆栈或队列)  。

以上就是本期的全部内容了,我们下次见咯! 

分享一张壁纸: 

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号