【图】概念、存储结构、广度优先遍历遍历、深度优先遍历 - 详解

痛定思痛。 2023-09-27 20:49 147阅读 0赞

目录

前言

一、图

1.1、基本概念

二、图的存储结构

2.1、存储结构

2.1、邻接矩阵(考察重点)

2.1.1、代码实现

2.2、邻接表

2.3.1、无向邻接表存储

2.3.2、有向图邻接表存储

3.1、图的广度优先遍历(层序遍历)

3.2、图的深度优先遍历


前言


本章主要讲的是图的基本概念以及应用,面试的时候基本不考图~

一、图

1.1、基本概念

图是由顶点集合及顶点间的关系组成的一种数据结构:G = (V, E),具体的:

  • 顶点集合V = {x|x属于某个数据对象集}是有穷非空集合;
  • E = {(x,y)|x,y属于V}或者E = {|x,y属于V && Path(x, y)}是顶点间关系的有穷集合,也叫做边的集 合。

(x, y)表示 x 到 y 的一条双向路径,即(x, y)是无方向的;Path表示从x到y的一条单向通路,即Path是有方向的。例如下图:

f31ce11f59dc4132bb15349eff3c7543.png

顶点和边:图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边, 图中的第k条边记作ek,ek = (vi,vj)或,。例如下图:

fdb55d9c132f4fb6ac373bfa7d3129cf.png

有向图和无向图:

  • 在有向图中,顶点对是有序的,顶点对,y>称为顶点x到顶点y的一条边(弧),和是两条不同的边;
  • 在无向图中,顶点对(x, y)是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x)是同一条边;

例如下图:

2f23907b472c479086536cd864d4c501.png

完全图:

  • 在有n个顶点的无向图中,若有n * (n - 1) / 2条边,即任意两个顶点之间有且仅有一条边,则称此图为 无向完全图;
  • 在n个顶点的有向图中,若有n * (n-1)条边,即任意两个顶点之间有且仅有方向 相反的边,则称此图为有向完全图;

例如下图:

285f7397133d4b369e5106b3efdebee6.png

4eedea1490d542908431bca641652fc7.png

邻接顶点:

  • 在无向图中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u和v;
  • 在有向图G中,若是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶点u,并称边与顶点u和顶点v相关联。

顶点的度:

顶点v的度是指与它相关联的边的条数(类似树的度),记作deg(v)。在有向图中,顶点的度等于该顶点的入度与 出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度是以v为起始点的有向 边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。注意:对于无向图,顶点的度等于该顶 点的入度和出度,即dev(v) = indev(v) = outdev(v)。

路径:在图G = (V, E)中,若从顶点vi出发有一组边使其可到达顶点vj,则称顶点vi到顶点vj的顶点序列为从 顶点vi到顶点vj的路径。

路径长度:

  • 对于不带权的图,一条路径的路径长度是指该路径上的边的条数;
  • 对于带权的图,一条路径的路 径长度是指该路径上各个边权值的总和。

例如下图:

4b8ff32514ed4b96a9e957e8a9a44e45.png

简单路径与回路:若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路径。回路或环:路径上第一个顶点v1和最后一个顶点vm重合。

例如下图:

eb157ac9b75149d889297aecfcfbc4cc.png

子图:设图G = {V, E}和图G1 = {V1,E1},若V1属于V且E1属于E,则称G1是G的子图

例如下图:

8a979448e7754f1e906667d4267ff046.png

连通图:无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图。

例如下图:

ab19bdea2075429f86b2e4170050aed9.png

强连通图:有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到 vi的路 径,则称此图是强连通图。

例如下图:

4c460a246a9d45c7beba542efed90a32.png

生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条 边。

二、图的存储结构


2.1、存储结构

在图的存储中,只需要保存:节点和边的关系即可~

2.1、邻接矩阵(考察重点)

邻接矩阵就是用矩阵(二维数组)来表示图的节点和边的关系,其中用 1 表示连通,0表示不连通.

如下无权值:

47962cd03bff4a01a5d77f5d1d5a4274.png

如下有权值:

7b96781a2d2748188ed069e5827301d8.png

2.1.1、代码实现

  1. import java.util.Arrays;
  2. public class GraphByMatrix {
  3. private char[] arrayV;//顶点数组
  4. private int[][] matrix;//邻接矩阵
  5. private boolean isDirect;//是否是有向图
  6. /**
  7. * @param size 代表当前顶点的个数
  8. * @param isDirect
  9. */
  10. public GraphByMatrix(int size, boolean isDirect) {
  11. this.arrayV = new char[size];
  12. matrix = new int[size][size];
  13. //这里约定:统一初始化为最大值
  14. for(int i = 0; i < size; i++) {
  15. Arrays.fill(matrix[i], Constant.MAX);
  16. }
  17. this.isDirect = isDirect;
  18. }
  19. /**
  20. * 初始化顶点
  21. * @param array
  22. */
  23. public void initArrayV(char[] array) {
  24. for(int i = 0; i < array.length; i++) {
  25. arrayV[i] = array[i];
  26. }
  27. }
  28. /**
  29. * 增加边
  30. * @param srcV 起点
  31. * @param destV 终点
  32. * @param weight 权值
  33. */
  34. public void addEdge(char srcV, char destV, int weight) {
  35. int srcIndex = getIndexOfV(srcV);
  36. int destIndex = getIndexOfV(destV);
  37. matrix[srcIndex][destIndex] = weight;
  38. //如果是无向图,那么相反的位置,也同样需要置为空
  39. if(!isDirect) {
  40. matrix[destIndex][srcIndex] = weight;
  41. }
  42. }
  43. /**
  44. * 获取顶点的下标
  45. * @param v
  46. * @return
  47. */
  48. private int getIndexOfV(char v) {
  49. for(int i = 0; i < arrayV.length; i++) {
  50. if(arrayV[i] == v)
  51. return i;
  52. }
  53. return -1;
  54. }
  55. /**
  56. * 获取顶点的度,有向图 = 入度 + 出度
  57. * @return
  58. */
  59. public int getDevofV(char v) {
  60. int count = 0;
  61. int srcIndex = getIndexOfV(v);
  62. for(int i = 0; i < arrayV.length; i++) {
  63. if(matrix[srcIndex][i] != Constant.MAX) {
  64. count++;
  65. }
  66. }
  67. //计算有向图的入度
  68. if(isDirect) {
  69. for(int i = 0; i < arrayV.length; i++) {
  70. if(matrix[i][srcIndex] != Constant.MAX) {
  71. count++;
  72. }
  73. }
  74. }
  75. return count;
  76. }
  77. //打印数组
  78. public void printGraph() {
  79. for(int i = 0; i < matrix.length; i++) {
  80. for(int j = 0; j < matrix[i].length; j++) {
  81. if(matrix[i][j] == Constant.MAX) {
  82. System.out.print("∞ ");
  83. } else {
  84. System.out.print(matrix[i][j] + " ");
  85. }
  86. }
  87. System.out.println();
  88. }
  89. }
  90. //测试
  91. public static void main(String[] args) {
  92. GraphByMatrix graph = new GraphByMatrix(4, true);
  93. char[] array = {'A', 'B', 'C', 'D'};
  94. graph.initArrayV(array);
  95. graph.addEdge('A', 'B', 1);
  96. graph.addEdge('A', 'D', 1);
  97. graph.addEdge('B', 'A', 1);
  98. graph.addEdge('B', 'C', 1);
  99. graph.addEdge('C', 'B', 1);
  100. graph.addEdge('C', 'D', 1);
  101. graph.addEdge('D', 'A', 1);
  102. graph.addEdge('D', 'C', 1);
  103. graph.printGraph();
  104. //获取 A 的度
  105. System.out.println(graph.getDevofV('A'));
  106. }
  107. }

2.2、邻接表

邻接表:使用数组表示顶点的集合,使用链表表示边的关系 。

2.3.1、无向邻接表存储

5f02d24cc18f489cbd3bff0c65eaf781.png

Ps:无向图中同一条边在邻接表中出现了两次。如果想知道顶点vi的度,只需要知道顶点vi边链表集 合中结点的数目即可。

2.3.2、有向图邻接表存储

22a619ce341c4b5eb9715016152002de.png

Ps:有向图中每条边在邻接表中只出现一次,与顶点vi对应的邻接表所含结点的个数,就是该顶点的 出度,也称出度表,要得到vi顶点的入度,必须检测其他所有顶点对应的边链表,看有多少边顶点的dst取值是i。

  1. import java.util.ArrayList;
  2. public class GraphByNode {
  3. static class Node {
  4. public int src;//起始位置
  5. public int dest;//目标位置
  6. public int weight;//权重
  7. public Node next;
  8. public Node(int src, int dest, int weight) {
  9. this.src = src;
  10. this.dest = dest;
  11. this.weight = weight;
  12. }
  13. }
  14. public char[] arrayV;
  15. public ArrayList<Node> edgList;//存储边
  16. public boolean isDirect;
  17. public GraphByNode(int size, boolean isDirect) {
  18. this.arrayV = new char[size];
  19. edgList = new ArrayList<>(size);
  20. for(int i = 0; i < size; i++) {
  21. edgList.add(null);
  22. }
  23. this.isDirect = isDirect;
  24. }
  25. /**
  26. * 初始化顶点
  27. * @param array
  28. */
  29. public void initArrayV(char[] array) {
  30. for(int i = 0; i < array.length; i++) {
  31. arrayV[i] = array[i];
  32. }
  33. }
  34. /**
  35. * 增加边
  36. * @param srcV 起点
  37. * @param destV 终点
  38. * @param weight 权值
  39. */
  40. public void addEdge(char srcV, char destV, int weight) {
  41. int srcIndex = getIndexOfV(srcV);
  42. int destIndex = getIndexOfV(destV);
  43. addEdgeChild(srcIndex, destIndex, weight);
  44. //无向图需要添加两条边
  45. if(!isDirect) {
  46. addEdgeChild(destIndex, srcIndex, weight);
  47. }
  48. }
  49. private void addEdgeChild(int srcIndex, int destIndex, int weight) {
  50. //这里拿到的是头结点
  51. Node cur = edgList.get(srcIndex);
  52. while(cur != null) {
  53. if(cur.dest == destIndex) {
  54. return;
  55. }
  56. cur = cur.next;
  57. }
  58. //之前没有存储过这条边
  59. Node node = new Node(srcIndex, destIndex, weight);
  60. node.next = edgList.get(srcIndex);
  61. edgList.set(srcIndex, node);
  62. }
  63. /**
  64. * 获取顶点的下标
  65. * @param v
  66. * @return
  67. */
  68. private int getIndexOfV(char v) {
  69. for(int i = 0; i < arrayV.length; i++) {
  70. if(arrayV[i] == v)
  71. return i;
  72. }
  73. return -1;
  74. }
  75. //获取顶点的度
  76. public int getDevOfv(char v) {
  77. int count = 0;
  78. int srcIndex = getIndexOfV(v);
  79. Node cur = edgList.get(srcIndex);
  80. while(cur != null) {
  81. count++;
  82. cur = cur.next;
  83. }
  84. //有向图
  85. if(isDirect) {
  86. int destIndex = srcIndex;
  87. for(int i = 0; i < arrayV.length; i++) {
  88. if(i == destIndex) {
  89. continue;
  90. } else {
  91. Node pCur = edgList.get(i);
  92. while(pCur != null) {
  93. if(pCur.dest == destIndex) {
  94. count++;
  95. }
  96. pCur = pCur.next;
  97. }
  98. }
  99. }
  100. }
  101. return count;
  102. }
  103. public void printGraph() {
  104. for(int i = 0; i < arrayV.length; i++) {
  105. System.out.print(arrayV[i] + "->");
  106. Node cur = edgList.get(i);
  107. while(cur != null) {
  108. System.out.print(arrayV[cur.dest] + " ->");
  109. cur = cur.next;
  110. }
  111. System.out.println();
  112. }
  113. }
  114. //测试
  115. public static void main(String[] args) {
  116. GraphByNode graph = new GraphByNode(4, true);
  117. char[] array = {'A', 'B', 'C', 'D'};
  118. graph.initArrayV(array);
  119. graph.addEdge('A', 'B', 1);
  120. graph.addEdge('A', 'D', 1);
  121. graph.addEdge('B', 'A', 1);
  122. graph.addEdge('B', 'C', 1);
  123. graph.addEdge('C', 'B', 1);
  124. graph.addEdge('C', 'D', 1);
  125. graph.addEdge('D', 'A', 1);
  126. graph.addEdge('D', 'C', 1);
  127. System.out.println("getDevOfV:" + graph.getDevOfv('A'));
  128. graph.printGraph();
  129. }
  130. }

3.1、图的广度优先遍历(层序遍历)

6388eabe5bc54b08a50a4a00e4660d5a.png

类似于二叉树的层序遍历,也需要使用队列,首先将第一个元素(假设如上图中的B)放入队列,同时使用一个 boolean 类型数组标记这个元素已被放入(这样做是为了防止一个元素被多次放入),接着出队(将 B 弹出),将出队的元素的连通的结点(A,C结点)经检验不重复后(boolean 类型数组检验是否之前被放入过),再次放入队列,依次往复即可~

  1. import java.util.Arrays;
  2. import java.util.LinkedList;
  3. import java.util.Queue;
  4. public class GraphByMatrix {
  5. private char[] arrayV;//顶点数组
  6. private int[][] matrix;//邻接矩阵
  7. private boolean isDirect;//是否是有向图
  8. /**
  9. * @param size 代表当前顶点的个数
  10. * @param isDirect
  11. */
  12. public GraphByMatrix(int size, boolean isDirect) {
  13. this.arrayV = new char[size];
  14. matrix = new int[size][size];
  15. //这里约定:统一初始化为最大值
  16. for(int i = 0; i < size; i++) {
  17. Arrays.fill(matrix[i], Constant.MAX);
  18. }
  19. this.isDirect = isDirect;
  20. }
  21. /**
  22. * 初始化顶点
  23. * @param array
  24. */
  25. public void initArrayV(char[] array) {
  26. for(int i = 0; i < array.length; i++) {
  27. arrayV[i] = array[i];
  28. }
  29. }
  30. /**
  31. * 增加边
  32. * @param srcV 起点
  33. * @param destV 终点
  34. * @param weight 权值
  35. */
  36. public void addEdge(char srcV, char destV, int weight) {
  37. int srcIndex = getIndexOfV(srcV);
  38. int destIndex = getIndexOfV(destV);
  39. matrix[srcIndex][destIndex] = weight;
  40. //如果是无向图,那么相反的位置,也同样需要置为空
  41. if(!isDirect) {
  42. matrix[destIndex][srcIndex] = weight;
  43. }
  44. }
  45. /**
  46. * 获取顶点的下标
  47. * @param v
  48. * @return
  49. */
  50. private int getIndexOfV(char v) {
  51. for(int i = 0; i < arrayV.length; i++) {
  52. if(arrayV[i] == v)
  53. return i;
  54. }
  55. return -1;
  56. }
  57. /**
  58. * 获取顶点的度,有向图 = 入度 + 出度
  59. * @return
  60. */
  61. public int getDevofV(char v) {
  62. int count = 0;
  63. int srcIndex = getIndexOfV(v);
  64. for(int i = 0; i < arrayV.length; i++) {
  65. if(matrix[srcIndex][i] != Constant.MAX) {
  66. count++;
  67. }
  68. }
  69. //计算有向图的入度
  70. if(isDirect) {
  71. for(int i = 0; i < arrayV.length; i++) {
  72. if(matrix[i][srcIndex] != Constant.MAX) {
  73. count++;
  74. }
  75. }
  76. }
  77. return count;
  78. }
  79. //打印数组
  80. public void printGraph() {
  81. for(int i = 0; i < matrix.length; i++) {
  82. for(int j = 0; j < matrix[i].length; j++) {
  83. if(matrix[i][j] == Constant.MAX) {
  84. System.out.print("∞ ");
  85. } else {
  86. System.out.print(matrix[i][j] + " ");
  87. }
  88. }
  89. System.out.println();
  90. }
  91. }
  92. //广度优先遍历
  93. public void bfs(char v) {
  94. //数组用来标记顶点是否被使用过
  95. boolean[] visited = new boolean[arrayV.length];
  96. //定义一个队列,来辅助完成 bfs
  97. Queue<Integer> queue = new LinkedList<>();
  98. int srcIndex = getIndexOfV(v);
  99. queue.offer(srcIndex);
  100. while(!queue.isEmpty()) {
  101. int top = queue.poll();
  102. System.out.print(arrayV[top] + "->");
  103. visited[top] = true;//每次弹出一个元素就置为 true
  104. for(int i = 0; i < arrayV.length; i++) {
  105. if(matrix[top][i] != Constant.MAX && !visited[i]) {
  106. queue.offer(i);
  107. visited[i] = true;//每次入队就置为true
  108. }
  109. }
  110. }
  111. }
  112. //测试
  113. public static void main(String[] args) {
  114. GraphByMatrix graph = new GraphByMatrix(4, false);
  115. char[] array = {'A', 'B', 'C', 'D'};
  116. graph.initArrayV(array);
  117. graph.addEdge('A', 'B', 1);
  118. graph.addEdge('A', 'D', 1);
  119. graph.addEdge('B', 'A', 1);
  120. graph.addEdge('B', 'C', 1);
  121. graph.addEdge('C', 'B', 1);
  122. graph.addEdge('C', 'D', 1);
  123. graph.addEdge('D', 'A', 1);
  124. graph.addEdge('D', 'C', 1);
  125. graph.bfs('B');
  126. }
  127. }

3.2、图的深度优先遍历

9e20e66f99574abeb289541ca8b3eedf.png

类似于二叉树的前序遍历,通过邻接矩阵进行优先遍历,例如起始位置为上图中的 B,那么上图中右边的邻接矩阵从竖列 B 元素开始向右,找到第一个不为 0 的,也就是通向 A 元素,那么接着再从竖列 A 元素开始向右,找到第一个不为 0 的,这里是 B 元素,但由于之前通向过 B 元素所以继续向右找不为 0 的,于此类推,最后得出 dfs 结果,如下代码

  1. import java.util.Arrays;
  2. import java.util.LinkedList;
  3. import java.util.Queue;
  4. public class GraphByMatrix {
  5. private char[] arrayV;//顶点数组
  6. private int[][] matrix;//邻接矩阵
  7. private boolean isDirect;//是否是有向图
  8. /**
  9. * @param size 代表当前顶点的个数
  10. * @param isDirect
  11. */
  12. public GraphByMatrix(int size, boolean isDirect) {
  13. this.arrayV = new char[size];
  14. matrix = new int[size][size];
  15. //这里约定:统一初始化为最大值
  16. for(int i = 0; i < size; i++) {
  17. Arrays.fill(matrix[i], Constant.MAX);
  18. }
  19. this.isDirect = isDirect;
  20. }
  21. /**
  22. * 初始化顶点
  23. * @param array
  24. */
  25. public void initArrayV(char[] array) {
  26. for(int i = 0; i < array.length; i++) {
  27. arrayV[i] = array[i];
  28. }
  29. }
  30. /**
  31. * 增加边
  32. * @param srcV 起点
  33. * @param destV 终点
  34. * @param weight 权值
  35. */
  36. public void addEdge(char srcV, char destV, int weight) {
  37. int srcIndex = getIndexOfV(srcV);
  38. int destIndex = getIndexOfV(destV);
  39. matrix[srcIndex][destIndex] = weight;
  40. //如果是无向图,那么相反的位置,也同样需要置为空
  41. if(!isDirect) {
  42. matrix[destIndex][srcIndex] = weight;
  43. }
  44. }
  45. /**
  46. * 获取顶点的下标
  47. * @param v
  48. * @return
  49. */
  50. private int getIndexOfV(char v) {
  51. for(int i = 0; i < arrayV.length; i++) {
  52. if(arrayV[i] == v)
  53. return i;
  54. }
  55. return -1;
  56. }
  57. /**
  58. * 获取顶点的度,有向图 = 入度 + 出度
  59. * @return
  60. */
  61. public int getDevofV(char v) {
  62. int count = 0;
  63. int srcIndex = getIndexOfV(v);
  64. for(int i = 0; i < arrayV.length; i++) {
  65. if(matrix[srcIndex][i] != Constant.MAX) {
  66. count++;
  67. }
  68. }
  69. //计算有向图的入度
  70. if(isDirect) {
  71. for(int i = 0; i < arrayV.length; i++) {
  72. if(matrix[i][srcIndex] != Constant.MAX) {
  73. count++;
  74. }
  75. }
  76. }
  77. return count;
  78. }
  79. //打印数组
  80. public void printGraph() {
  81. for(int i = 0; i < matrix.length; i++) {
  82. for(int j = 0; j < matrix[i].length; j++) {
  83. if(matrix[i][j] == Constant.MAX) {
  84. System.out.print("∞ ");
  85. } else {
  86. System.out.print(matrix[i][j] + " ");
  87. }
  88. }
  89. System.out.println();
  90. }
  91. }
  92. //广度优先遍历
  93. public void bfs(char v) {
  94. //数组用来标记顶点是否被使用过
  95. boolean[] visited = new boolean[arrayV.length];
  96. //定义一个队列,来辅助完成 bfs
  97. Queue<Integer> queue = new LinkedList<>();
  98. int srcIndex = getIndexOfV(v);
  99. queue.offer(srcIndex);
  100. while(!queue.isEmpty()) {
  101. int top = queue.poll();
  102. System.out.print(arrayV[top] + "->");
  103. visited[top] = true;//每次弹出一个元素就置为 true
  104. for(int i = 0; i < arrayV.length; i++) {
  105. if(matrix[top][i] != Constant.MAX && !visited[i]) {
  106. queue.offer(i);
  107. visited[i] = true;//每次入队就置为true
  108. }
  109. }
  110. }
  111. }
  112. // 深度优先遍历
  113. public void dfs(char v) {
  114. //数组用来标记顶点是否被使用过
  115. boolean[] visited = new boolean[arrayV.length];
  116. int srcIndex = getIndexOfV(v);
  117. dfsChild(srcIndex, visited);
  118. }
  119. private void dfsChild(int srcIndex, boolean[] visited) {
  120. System.out.print(arrayV[srcIndex] + "->");
  121. visited[srcIndex] = true;
  122. for(int i = 0; i < arrayV.length; i++) {
  123. if(matrix[srcIndex][i] != Constant.MAX && !visited[i]) {
  124. dfsChild(i, visited);
  125. }
  126. }
  127. }
  128. //测试
  129. public static void main(String[] args) {
  130. GraphByMatrix graph = new GraphByMatrix(4, false);
  131. char[] array = {'A', 'B', 'C', 'D'};
  132. graph.initArrayV(array);
  133. graph.addEdge('A', 'B', 1);
  134. graph.addEdge('A', 'D', 1);
  135. graph.addEdge('B', 'A', 1);
  136. graph.addEdge('B', 'C', 1);
  137. graph.addEdge('C', 'B', 1);
  138. graph.addEdge('C', 'D', 1);
  139. graph.addEdge('D', 'A', 1);
  140. graph.addEdge('D', 'C', 1);
  141. // graph.bfs('B');
  142. graph.dfs('B');
  143. }
  144. }

4d7fde5b394e42f2afd0906eb96d3c54.gif

发表评论

表情:
评论列表 (有 0 条评论,147人围观)

还没有评论,来说两句吧...

相关阅读