image.png

:::info 前言:用邻接矩阵和邻接表两种图的存储形式实现DFS、BFS算法,并附例子实现。
总的来说,邻接矩阵比较好处理,没有邻接表处理那么复杂,但是数组永远不能规避的一个缺点就是内存的占用较邻接表高。 :::

一、深度优先搜索算法(Depth-First-Search)

算法说明

访问步骤:

  • 访问顶点v;
  • 依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
  • 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。

核心代码就是利用递归,以及标志数组的设定,每次访问数组元素的那一行,对那行链表进行遍历,每遍历一个链表结点,就将“其”所在的那个数组元素“点亮”。如果标志数组里面的所有元素都被访问了,说明遍历完了

深度优先搜索类似于树里面遍历算法当中的先序遍历。

邻接矩阵的DFS代码

以这个无向图为例
image.png
image.png

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define MVNum 100
  4. #define MaxInt 32767
  5. typedef char VerTexType;
  6. typedef int ArcType;
  7. /**
  8. * 邻接矩阵存储形式
  9. */
  10. typedef struct {
  11. /* data */
  12. VerTexType vexs[MVNum]; //顶点表
  13. ArcType arcs[MVNum][MVNum]; //邻接矩阵
  14. int vexnum, arcnum; //图的当前顶点和边数
  15. }AMGraph;
  16. /**
  17. * 确定v在G中的位置,即顶点数组的下标
  18. */
  19. int LocateVex(AMGraph &G, char v) {
  20. for (int i = 0; i < G.vexnum;i++) {
  21. if (v == G.vexs[i]){
  22. return i;
  23. }
  24. }
  25. }
  26. /**
  27. * 如果创建无向图
  28. */
  29. void CreateUDN(AMGraph &G) {
  30. // 采用邻接矩阵表示法,创建无向图G
  31. cout << "请输入顶点数和边数:" << endl;
  32. cin >> G.vexnum >> G.arcnum; //输入顶点数和边数
  33. // 初始化顶点
  34. for (int i = 0; i < G.vexnum;i++){
  35. cout << "请输入第" << i << "个顶点值" << endl;
  36. cin >> G.vexs[i];
  37. }
  38. // 初始化邻接矩阵的边的权值为最大值
  39. for (int i = 0; i < G.vexnum;i++) {
  40. for (int j = 0; j < G.vexnum;j++) {
  41. G.arcs[i][j] = 0;
  42. }
  43. }
  44. // 构造邻接矩阵
  45. for (int k = 0; k < G.arcnum;k++) {
  46. cout << "请输入每条边所依附的顶点:" << endl;
  47. char v1, v2;
  48. int w = 1; //一条边所依附的顶点和权值
  49. cin >> v1 >> v2;
  50. int i = LocateVex(G, v1);
  51. int j = LocateVex(G, v2);
  52. G.arcs[i][j] = w;
  53. G.arcs[j][i] = w;
  54. }
  55. }
  56. /**
  57. * 打印输出图
  58. */
  59. void Display(AMGraph &G) {
  60. for (int i = 0; i < G.vexnum;i++) {
  61. for (int j = 0; j < G.vexnum;j++) {
  62. cout << G.arcs[i][j] << " ";
  63. }
  64. cout << endl;
  65. }
  66. }
  67. //----邻接矩阵的DFS遍历----
  68. //访问标志数组,其初值为false
  69. bool visited[MVNum];
  70. /**
  71. * 图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
  72. */
  73. void DFS_AM(AMGraph &G, int v) {
  74. //访问第v个顶点,并置访问标志数组相应分量值为true
  75. cout<<v;
  76. visited[v] = true;
  77. //依次检查邻接矩阵v所在的行
  78. for(int w = 0; w < G.vexnum; w++)
  79. //G.arcs[v][w] != 0表示w是v的邻接点,!visited[w]表示未访问到
  80. if((G.arcs[v][w] != 0) && (!visited[w]))
  81. DFS_AM(G, w); //递归调用DFS_AM
  82. }
  83. /**
  84. * 图G的储存类型任意,对非连通图G做深度优先遍历
  85. */
  86. void DFSTraverse(AMGraph &G) {
  87. //访问标志数组初始化
  88. for(int v = 0; v < G.vexnum; v++)
  89. visited[v] = false;
  90. //循环调用DFS
  91. for(int v = 0; v < G.vexnum; v++)
  92. if(!visited[v])
  93. DFS_AM(G, v); //对尚未访问的顶点调用DFS
  94. }
  95. int main() {
  96. AMGraph test;
  97. CreateUDN(test);
  98. Display(test);
  99. DFSTraverse(test);
  100. return 0;
  101. }

邻接表的DFS代码

举之前上课的一张PPT例子(元素插入为后插法)
image.png
结果
image.png

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define MVNum 100
  4. #define MaxInt 32767
  5. typedef char VerTexType;
  6. typedef int OtherInfo;
  7. /**
  8. * 邻接表存储
  9. */
  10. /**
  11. * 存储结构
  12. */
  13. typedef struct ArcNode { //边结点
  14. int adjvex; //该边所指向的结点的位置
  15. struct ArcNode *nextarc; //指向下一条边的指针
  16. OtherInfo info; //和边相关的其他信息
  17. }ArcNode;
  18. typedef struct VNode { //顶点信息
  19. VerTexType data; //数据域,存放顶点vi的名称或其他有关信息
  20. ArcNode *firstarc; //指向第一条依附该顶点的边的指针
  21. }VNode, AdjList[MVNum]; //AdjList表示邻接表的类型
  22. typedef struct {
  23. AdjList vertices;
  24. int vexnum, arcnum; //图当前的顶点数和边数
  25. }ALGragh; //邻接表(Adjacency List)
  26. /**
  27. * 找到v顶点在图的顶点数组中的位置
  28. */
  29. int LocateVex(ALGragh &G, char v) {
  30. for (int i = 0; i < G.vexnum;i++) {
  31. if (v == G.vertices[i].data) {
  32. return i;
  33. }
  34. }
  35. }
  36. /**
  37. * 邻接表创建无向图
  38. */
  39. void CreateUDG(ALGragh &G) {
  40. cout << "请输入顶点数和边数:" << endl;
  41. cin >> G.vexnum >> G.arcnum; // 邻接表的顶点数和边数
  42. // 初始化顶点数组
  43. for (int i = 0; i < G.vexnum;i++) {
  44. cin >> G.vertices[i].data; // 初始化顶点数组里面的结点data
  45. G.vertices[i].firstarc = NULL; // 初始化顶点数组里面的结点next域
  46. }
  47. // 初始化所有的边
  48. for (int k = 0; k < G.arcnum;k++) {
  49. char v1, v2;
  50. cout << "请输入每条边所依附的顶点:" << endl;
  51. cin >> v1 >> v2;
  52. int i = LocateVex(G, v1); // 找到v1在顶点数组的下标
  53. int j = LocateVex(G, v2); // 找到v2在顶点数组的下标
  54. // 下面建立p1和p2是因为无向图,如果是有向图就没必要了只需要p1
  55. // 前插
  56. ArcNode *p1 = new ArcNode;
  57. p1->adjvex = j;
  58. p1->nextarc = G.vertices[i].firstarc;
  59. G.vertices[i].firstarc = p1;
  60. ArcNode *p2 = new ArcNode;
  61. p2->adjvex = i;
  62. p2->nextarc = G.vertices[j].firstarc;
  63. G.vertices[j].firstarc = p2;
  64. }
  65. }
  66. /**
  67. * 打印输出图
  68. */
  69. void Display(ALGragh &G) {
  70. for (int i = 0; i < G.vexnum;i++) {
  71. cout << "结点" << i << ":";
  72. // 复制选中的节点数组中的结点
  73. VNode p;
  74. p = G.vertices[i];
  75. if (p.firstarc != NULL){
  76. ArcNode *temp;
  77. temp = G.vertices[i].firstarc;
  78. while (temp != NULL) {
  79. cout << temp->adjvex<<" ";
  80. temp = temp->nextarc;
  81. }
  82. cout << "\n";
  83. }
  84. }
  85. }
  86. //----邻接表的DFS遍历----
  87. bool visited[MVNum]; //访问标志数组,其初值为false
  88. void DFS_AL(ALGragh G, int v)
  89. {//图G为邻接表类型,从从第v个顶点出发深度优先搜索遍历图G
  90. cout<<v; //访问第v个顶点,并置访问标志数组相应分量值为true
  91. visited[v] = true;
  92. ArcNode *p;
  93. p = G.vertices[v].firstarc; //p指向v的边链表的第一个边结点
  94. while(p != NULL)
  95. {
  96. int w = p->adjvex; //w是v的邻接点
  97. if(!visited[w]) //如果w未访问
  98. DFS_AL(G, w); //递归调用DFS_AL
  99. p = p->nextarc; //p指向下一个结点
  100. }
  101. }
  102. void DFSTraverse(ALGragh G)
  103. {//图G的储存类型任意,对非连通图G做深度优先遍历
  104. for(int v = 0; v < G.vexnum; v++) //访问标志数组初始化
  105. visited[v] = false;
  106. for(int v = 0; v < G.vexnum; v++) //循环调用DFS
  107. if(!visited[v])
  108. DFS_AL(G, v); //对尚未访问的顶点调用DFS
  109. }
  110. int main() {
  111. ALGragh test;
  112. CreateUDG(test);
  113. // Display(test);
  114. DFSTraverse(test);
  115. }

二、广度优先搜索算法(Breadth-First-Search)

算法说明

从某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到。

若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

在树遍历中类似层次遍历。

邻接矩阵的BFS代码

还是这个例子
image.png
image.png

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define MVNum 100
  4. #define MaxInt 32767
  5. typedef char VerTexType;
  6. typedef int ArcType;
  7. /**
  8. * 邻接矩阵的bfs代码
  9. */
  10. typedef struct {
  11. /* data */
  12. VerTexType vexs[MVNum]; //顶点表
  13. ArcType arcs[MVNum][MVNum]; //邻接矩阵
  14. int vexnum, arcnum; //图的当前顶点和边数
  15. }AMGraph;
  16. /**
  17. * 确定v在G中的位置,即顶点数组的下标
  18. */
  19. int LocateVex(AMGraph &G, char v) {
  20. for (int i = 0; i < G.vexnum;i++) {
  21. if (v == G.vexs[i]){
  22. return i;
  23. }
  24. }
  25. }
  26. /**
  27. * 创建无向网
  28. * 如果创建无向图
  29. */
  30. void CreateUDN(AMGraph &G) {
  31. // 采用邻接矩阵表示法,创建无向图G
  32. cout << "请输入顶点数和边数:" << endl;
  33. cin >> G.vexnum >> G.arcnum; //输入顶点数和边数
  34. // 初始化顶点
  35. for (int i = 0; i < G.vexnum;i++){
  36. cout << "请输入第" << i << "个顶点值" << endl;
  37. cin >> G.vexs[i];
  38. }
  39. // 初始化邻接矩阵的边的权值为最大值
  40. for (int i = 0; i < G.vexnum;i++) {
  41. for (int j = 0; j < G.vexnum;j++) {
  42. G.arcs[i][j] = 0;
  43. }
  44. }
  45. // 构造邻接矩阵
  46. for (int k = 0; k < G.arcnum;k++) {
  47. cout << "请输入每条边所依附的顶点:" << endl;
  48. char v1, v2;
  49. int w = 1; //一条边所依附的顶点和权值
  50. cin >> v1 >> v2;
  51. int i = LocateVex(G, v1);
  52. int j = LocateVex(G, v2);
  53. G.arcs[i][j] = w;
  54. G.arcs[j][i] = w;
  55. }
  56. }
  57. /**
  58. * 打印输出图
  59. */
  60. void Display(AMGraph &G) {
  61. for (int i = 0; i < G.vexnum;i++) {
  62. for (int j = 0; j < G.vexnum;j++) {
  63. cout << G.arcs[i][j] << " ";
  64. }
  65. cout << endl;
  66. }
  67. }
  68. //----邻接矩阵的BFS遍历----
  69. bool visited[MVNum];
  70. void BFS_AM(AMGraph G, int v)
  71. {//按广度优先非递归遍历连通图G
  72. cout<<v;
  73. visited[v] = true; //访问第v个顶点,并置访问标志数组相应分量值为true
  74. queue<int> Q;
  75. Q.push(v);
  76. while(!Q.empty())
  77. {
  78. int u = Q.front(); //队头元素出队并置为u
  79. Q.pop();
  80. for(int w = 0; w < G.vexnum; w++)
  81. if((G.arcs[u][w] != 0) && (!visited[w])) //G.arcs[v][w] != 0表示w是v的邻接点,!visited[w]表示未访问到 //w为u的尚未访问的邻接顶点
  82. {
  83. cout<<w;
  84. visited[w] = true; //访问w,并置访问标志数组相应分量值为true
  85. Q.push(w); //w进队
  86. }
  87. }
  88. }
  89. void BFSTraverse(AMGraph &G) {
  90. //访问标志数组初始化
  91. for(int v = 0; v < G.vexnum; v++)
  92. visited[v] = false;
  93. //循环调用BFS
  94. for(int v = 0; v < G.vexnum; v++)
  95. if(!visited[v])
  96. BFS_AM(G, v); //对尚未访问的顶点调用BFS
  97. }
  98. int main() {
  99. AMGraph test;
  100. CreateUDN(test);
  101. Display(test);
  102. // DFSTraverse(test);
  103. BFSTraverse(test);
  104. return 0;
  105. }

邻接表的BFS代码

还用和DFS一样的例子
image.pngimage.png

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define MVNum 100
  4. #define MaxInt 32767
  5. typedef char VerTexType;
  6. typedef int OtherInfo;
  7. /**
  8. * 邻接表的bfs代码
  9. */
  10. /**
  11. * 存储结构
  12. */
  13. typedef struct ArcNode { //边结点
  14. int adjvex; //该边所指向的结点的位置
  15. struct ArcNode *nextarc; //指向下一条边的指针
  16. OtherInfo info; //和边相关的其他信息
  17. }ArcNode;
  18. typedef struct VNode { //顶点信息
  19. VerTexType data; //数据域,存放顶点vi的名称或其他有关信息
  20. ArcNode *firstarc; //指向第一条依附该顶点的边的指针
  21. }VNode, AdjList[MVNum]; //AdjList表示邻接表的类型
  22. typedef struct {
  23. AdjList vertices;
  24. int vexnum, arcnum; //图当前的顶点数和边数
  25. }ALGraph; //邻接表(Adjacency List)
  26. /**
  27. * 找到v顶点在图的顶点数组中的位置
  28. */
  29. int LocateVex(ALGraph &G, char v) {
  30. for (int i = 0; i < G.vexnum;i++) {
  31. if (v == G.vertices[i].data) {
  32. return i;
  33. }
  34. }
  35. }
  36. /**
  37. * 邻接表创建无向图
  38. */
  39. void CreateUDG(ALGraph &G) {
  40. cout << "请输入顶点数和边数:" << endl;
  41. cin >> G.vexnum >> G.arcnum; // 邻接表的顶点数和边数
  42. // 初始化顶点数组
  43. for (int i = 0; i < G.vexnum;i++) {
  44. cin >> G.vertices[i].data; // 初始化顶点数组里面的结点data
  45. G.vertices[i].firstarc = NULL; // 初始化顶点数组里面的结点next域
  46. }
  47. // 初始化所有的边
  48. for (int k = 0; k < G.arcnum;k++) {
  49. char v1, v2;
  50. cout << "请输入每条边所依附的顶点:" << endl;
  51. cin >> v1 >> v2;
  52. int i = LocateVex(G, v1); // 找到v1在顶点数组的下标
  53. int j = LocateVex(G, v2); // 找到v2在顶点数组的下标
  54. // 下面建立p1和p2是因为无向图,如果是有向图就没必要了只需要p1
  55. // 前插
  56. ArcNode *p1 = new ArcNode;
  57. p1->adjvex = j;
  58. p1->nextarc = G.vertices[i].firstarc;
  59. G.vertices[i].firstarc = p1;
  60. ArcNode *p2 = new ArcNode;
  61. p2->adjvex = i;
  62. p2->nextarc = G.vertices[j].firstarc;
  63. G.vertices[j].firstarc = p2;
  64. }
  65. }
  66. /**
  67. * 打印输出图
  68. */
  69. void Display(ALGraph &G) {
  70. for (int i = 0; i < G.vexnum;i++) {
  71. cout << "结点" << i << ":";
  72. // 复制选中的节点数组中的结点
  73. VNode p;
  74. p = G.vertices[i];
  75. if (p.firstarc != NULL){
  76. ArcNode *temp;
  77. temp = G.vertices[i].firstarc;
  78. while (temp != NULL) {
  79. cout << temp->adjvex<<" ";
  80. temp = temp->nextarc;
  81. }
  82. cout << "\n";
  83. }
  84. }
  85. }
  86. //----邻接表的BFS遍历----
  87. bool visited[MVNum];
  88. int FirstAdjvex(ALGraph& G, int u)
  89. {
  90. int w = G.vertices[u].firstarc->adjvex;
  91. return w;
  92. }
  93. int NextAdjVex(ALGraph& G, int u, int w)
  94. {
  95. ArcNode *temp = G.vertices[u].firstarc;
  96. while (temp->adjvex != w)
  97. {
  98. temp = temp->nextarc;
  99. }
  100. if (temp->nextarc)
  101. return temp->nextarc->adjvex;
  102. else
  103. return -1;
  104. delete temp;
  105. }
  106. void BFS_AL(ALGraph& G, int v){
  107. cout << v;
  108. visited[v] = true;
  109. queue<int> Q;
  110. Q.push(v);
  111. int u = v;
  112. while (!Q.empty()){
  113. u = Q.front();
  114. Q.pop();
  115. for (int w = FirstAdjvex(G, u); w >= 0; w = NextAdjVex(G, u, w)){
  116. if (!visited[w]){
  117. cout <<w;
  118. visited[w] = true;
  119. Q.push(w);
  120. }
  121. }
  122. }
  123. }
  124. void BFSTraverse(ALGraph &G) {
  125. //访问标志数组初始化
  126. for(int v = 0; v < G.vexnum; v++)
  127. visited[v] = false;
  128. //循环调用BFS
  129. for(int v = 0; v < G.vexnum; v++)
  130. if(!visited[v])
  131. BFS_AL(G, v); //对尚未访问的顶点调用BFS
  132. }
  133. int main() {
  134. ALGraph test;
  135. CreateUDG(test);
  136. Display(test);
  137. BFSTraverse(test);
  138. }

:::danger 【插眼】为啥我写的一个函数不需要队列也可以???直接将顶点数组的一个元素后面接的链表遍历不就好了,然后再遍历标志数组元素值部位true的不就好了。。。为啥要压队列呀?
莫不是哪里有隐藏的bug,插个眼!!! :::

图的遍历——DFS(深度优先)、BFS(广度优先) - 图10

:::success 【拔眼】这样是一种特殊情况,只适合图的各个结点是按照层次标号的,并且放入标志数组也是按照顺序放入的…… :::

插眼代码如下:

  1. void BFS_AL(ALGraph &G, int v)
  2. {//按广度优先非递归遍历连通图G
  3. cout<<v;
  4. visited[v] = true; //访问第v个顶点,并置访问标志数组相应分量值为true
  5. ArcNode *p;
  6. p = G.vertices[v].firstarc;
  7. if (p != NULL) {
  8. while(p != NULL) {
  9. if (!visited[p->adjvex]){
  10. cout << p->adjvex;
  11. }
  12. visited[p->adjvex] = true;
  13. p = p->nextarc;
  14. }
  15. }
  16. }