当前位置:   article > 正文

【数据结构Java】--图、BFS、DFS、拓扑结构_拓扑图数据结构

拓扑图数据结构

目录

一、图(Graph)

1.概念

2.有向图

 3.出度、入度

4.无向图

5.简单图、多重图

6.无向完全图

 7.有向完全图

8.有权图

9.连通图

 10.连通分量(无向图)

 11.强连通图(有向图)

12.强连通分量

 13.邻接矩阵

1.基本概念

2.邻接矩阵--有权图

14.邻接表

 1.基本概念 

 2.有权图

15.图的实现

图的基础接口

 添加边addEdge

图的总边(edgesSize)

图的点的接口

图的边的接口

删除边removeEdge

完整代码

二、图的遍历

新的图接口

三、广度优先搜索(BFS)

1.图解

​编辑 2.思路

3.代码实现

四、深度优先搜索(DFS)

1.前序遍历

 2.图解

3.递归实现

 4.通过栈-----非递归实现

1)步骤

2)图解

 3)代码实现

五、AOV网(Activity On Vertex Network)

六、拓扑排序(Topological Sort) 

1.简介

 2.思路

3.代码实现

一、图(Graph)

1.概念

2.有向图

 3.出度、入度

4.无向图

 

5.简单图、多重图

6.无向完全图

 7.有向完全图

8.有权图

 

9.连通图

 10.连通分量(无向图)

 11.强连通图(有向图)

12.强连通分量

 13.邻接矩阵

1.基本概念

2.邻接矩阵--有权图

 

14.邻接表

 1.基本概念 

 2.有权图

15.图的实现

图的基础接口

  1. package graph;
  2. public interface Graph<V,E> {
  3. // 边的数量
  4. int edgesSize();
  5. // 顶点数量
  6. int verticesSize();
  7. // 添加顶点
  8. void addVertex(V v);
  9. // 添加边(无权值)
  10. void addEdge(V from,V to);
  11. // 添加边(有权值)
  12. void addEdge(V from,V to,E weight);
  13. // 删除顶点
  14. void removeVertex(V v);
  15. // 删除边
  16. void removeEdge(V from,V to);
  17. interface vertexVisitor<V>{
  18. boolean visit(V v);
  19. }
  20. void print();
  21. }

 添加边addEdge

  1. /**
  2. * 添加无权值的边
  3. * @param from
  4. * @param to
  5. */
  6. @Override
  7. public void addEdge(V from, V to) {
  8. addEdge(from,to,null);
  9. }
  10. /**
  11. * 添加有权值的边
  12. * @param from
  13. * @param to
  14. * @param weight
  15. */
  16. @Override
  17. public void addEdge(V from, V to, E weight) {
  18. //根据传入的参数from找到出发点,如果不存在则创建
  19. Vertex<V,E> fromVertex=vertices.get(from);
  20. if (fromVertex==null){
  21. fromVertex=new Vertex<>(from);
  22. //将点和对应的点关系存入
  23. vertices.put(from,fromVertex);
  24. }
  25. //根据传入参数to找到终点,如果找不到则创建
  26. Vertex<V,E> toVertex=vertices.get(to);
  27. if (toVertex==null){
  28. toVertex=new Vertex<>(to);
  29. //将点和对应的点关系存入
  30. vertices.put(to,toVertex);
  31. }
  32. //根据出发点和终点,创建边
  33. Edge<V,E> edge=new Edge<>(fromVertex,toVertex);
  34. edge.weight=weight;//有权值加上,无权值则为null
  35. //不管原来是否存在,都先删除,在添加进去
  36. if (fromVertex.outEdges.contains(edge)){ //说明存在
  37. toVertex.inEdges.remove(edge);
  38. //在整个图中的边减少
  39. edges.remove(edge);
  40. }
  41. fromVertex.outEdges.add(edge);
  42. toVertex.inEdges.add(edge);
  43. //在整个图中的边增加
  44. edges.add(edge);
  45. }

图的总边(edgesSize)

  1. public int edgesSize() {
  2. return edges.size();
  3. }

图的点的接口

  1. /**
  2. * 顶点
  3. * @param <V>
  4. * @param <E>
  5. */
  6. private static class Vertex<V,E>{
  7. //顶点值
  8. V value;
  9. Set<Edge<V,E>> inEdges=new HashSet<>();
  10. Set<Edge<V,E>> outEdges=new HashSet<>();
  11. public Vertex(V value) {
  12. this.value = value;
  13. }
  14. /**
  15. * 比较两个顶点是否相等
  16. * @param obj
  17. * @return
  18. */
  19. @Override
  20. public boolean equals(Object obj) {
  21. return Objects.equals(value,((Vertex<V,E>)obj).value);
  22. }
  23. @Override
  24. public int hashCode() {
  25. return value==null ? 0: value.hashCode();
  26. }
  27. }

图的边的接口

  1. /**
  2. * 边
  3. * @param <V>
  4. * @param <E>
  5. */
  6. private static class Edge<V,E> {
  7. Vertex<V,E> from;
  8. Vertex<V,E> to;
  9. E weight;
  10. public Edge(Vertex<V, E> from, Vertex<V, E> to) {
  11. this.from = from;
  12. this.to = to;
  13. }
  14. /**
  15. * 判断两条边是否相同
  16. * @param obj
  17. * @return
  18. */
  19. @Override
  20. public boolean equals(Object obj) {
  21. Edge<V,E> edge=(Edge<V, E>) obj;
  22. return Objects.equals(from,edge.from) && Objects.equals(to,edge.to);
  23. }
  24. /**
  25. * 如果equals的结果是true说明一样,则hashcode值也一样
  26. * @return
  27. */
  28. @Override
  29. public int hashCode() {
  30. return from.hashCode()+to.hashCode();
  31. }
  32. }

删除边removeEdge

  1. /**
  2. * 删除边
  3. * @param from
  4. * @param to
  5. */
  6. @Override
  7. public void removeEdge(V from, V to) {
  8. // 根据传入的from获得起点,不存在则不需要删除
  9. Vertex<V,E> fromVertex=vertices.get(from);
  10. if (fromVertex ==null){
  11. return;
  12. }
  13. // 根据传入的to找到终点,不存在则不需要删除
  14. Vertex<V,E> toVertex=vertices.get(to);
  15. if (toVertex==null) return;
  16. // 根据起点和终点获得边,然后删除
  17. Edge<V,E> edge=new Edge<>(fromVertex,toVertex);
  18. if (fromVertex.outEdges.remove(edge)){ //表示删除成功
  19. toVertex.inEdges.remove(edge);
  20. edges.remove(edge);
  21. }
  22. }

完整代码

  1. package graph;
  2. import java.util.*;
  3. public class ListGraph<V,E> implements Graph<V,E>{
  4. // 传入的V与顶点类Vertex的映射【要记录该点的出入是inEdges还是outEdges】
  5. private Map<V, Vertex<V, E>> vertices = new HashMap<>();
  6. // 边的Set集合
  7. private Set<Edge<V, E>> edges = new HashSet<>();
  8. public void print(){
  9. System.out.println("[顶点]-------------------");
  10. vertices.forEach((V v, Vertex<V, E> vertex) -> {
  11. System.out.println(v);
  12. System.out.println("out-----------");
  13. System.out.println(vertex.outEdges);
  14. System.out.println("int-----------");
  15. System.out.println(vertex.inEdges);
  16. });
  17. System.out.println("[边]-------------------");
  18. edges.forEach((Edge<V, E> edge) -> {
  19. System.out.println(edge);
  20. });
  21. }
  22. @Override
  23. public int edgesSize() {
  24. return edges.size();
  25. }
  26. @Override
  27. public int verticesSize() {
  28. return vertices.size();
  29. }
  30. @Override
  31. public void addVertex(V v) {
  32. // put(key,value)
  33. vertices.put(v,new Vertex<>(v));
  34. }
  35. /**
  36. * 添加无权值的边
  37. * @param from
  38. * @param to
  39. */
  40. @Override
  41. public void addEdge(V from, V to) {
  42. addEdge(from,to,null);
  43. }
  44. /**
  45. * 添加有权值的边
  46. * @param from
  47. * @param to
  48. * @param weight
  49. */
  50. @Override
  51. public void addEdge(V from, V to, E weight) {
  52. //根据传入的参数from找到出发点,如果不存在则创建
  53. Vertex<V,E> fromVertex=vertices.get(from);
  54. if (fromVertex==null){
  55. fromVertex=new Vertex<>(from);
  56. //将点和对应的点关系存入
  57. vertices.put(from,fromVertex);
  58. }
  59. //根据传入参数to找到终点,如果找不到则创建
  60. Vertex<V,E> toVertex=vertices.get(to);
  61. if (toVertex==null){
  62. toVertex=new Vertex<>(to);
  63. //将点和对应的点关系存入
  64. vertices.put(to,toVertex);
  65. }
  66. //根据出发点和终点,创建边
  67. Edge<V,E> edge=new Edge<>(fromVertex,toVertex);
  68. edge.weight=weight;//有权值加上,无权值则为null
  69. //不管原来是否存在,都先删除,在添加进去
  70. if (fromVertex.outEdges.contains(edge)){ //说明存在
  71. toVertex.inEdges.remove(edge);
  72. //在整个图中的边减少
  73. edges.remove(edge);
  74. }
  75. fromVertex.outEdges.add(edge);
  76. toVertex.inEdges.add(edge);
  77. //在整个图中的边增加
  78. edges.add(edge);
  79. }
  80. /**
  81. * 删除点
  82. * @param v
  83. */
  84. @Override
  85. public void removeVertex(V v) {
  86. //根据传入的值找到点并且删除,不存在则不做操作
  87. Vertex<V,E> vertex=vertices.remove(v);
  88. if (vertex==null) return;
  89. // 迭代器遍历集合vertex.outEdges,删除所有从该点出去的边
  90. // iterator.hasNext():相当于i++
  91. for (Iterator<Edge<V,E>> iterator = vertex.inEdges.iterator();iterator.hasNext();){
  92. Edge<V,E> edge=iterator.next();//遍历从该点出去的边
  93. edge.to.inEdges.remove(edge);//获取终点进入的边,并从中删除遍历到的边
  94. //这个remove方法是将当前的这个边删除
  95. iterator.remove();//将当前遍历到当前遍历到的元素edge从集合vertex.outEdges中删除
  96. edges.remove(edge);
  97. }
  98. // 迭代器遍历集合vertex.inEdges, 删除所有进入该点的边
  99. for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {
  100. Edge<V, E> edge = iterator.next(); // 遍历到的进入该点的边
  101. edge.from.outEdges.remove(edge); // 获取起点出去的边,并从中删除遍历到的边
  102. iterator.remove(); // 将当前遍历到的元素edge从集合vertex.inEdges中删掉
  103. edges.remove(edge);
  104. }
  105. }
  106. /**
  107. * 删除边
  108. * @param from
  109. * @param to
  110. */
  111. @Override
  112. public void removeEdge(V from, V to) {
  113. // 根据传入的from获得起点,不存在则不需要删除
  114. Vertex<V,E> fromVertex=vertices.get(from);
  115. if (fromVertex ==null){
  116. return;
  117. }
  118. // 根据传入的to找到终点,不存在则不需要删除
  119. Vertex<V,E> toVertex=vertices.get(to);
  120. if (toVertex==null) return;
  121. // 根据起点和终点获得边,然后删除
  122. Edge<V,E> edge=new Edge<>(fromVertex,toVertex);
  123. if (fromVertex.outEdges.remove(edge)){ //表示删除成功
  124. toVertex.inEdges.remove(edge);
  125. edges.remove(edge);
  126. }
  127. }
  128. /**
  129. * 顶点
  130. * @param <V>
  131. * @param <E>
  132. */
  133. private static class Vertex<V,E>{
  134. //顶点值
  135. V value;
  136. Set<Edge<V,E>> inEdges=new HashSet<>();
  137. Set<Edge<V,E>> outEdges=new HashSet<>();
  138. public Vertex(V value) {
  139. this.value = value;
  140. }
  141. /**
  142. * 比较两个顶点是否相等
  143. * @param obj
  144. * @return
  145. */
  146. @Override
  147. public boolean equals(Object obj) {
  148. return Objects.equals(value,((Vertex<V,E>)obj).value);
  149. }
  150. @Override
  151. public int hashCode() {
  152. return value==null ? 0: value.hashCode();
  153. }
  154. }
  155. /**
  156. * 边
  157. * @param <V>
  158. * @param <E>
  159. */
  160. private static class Edge<V,E> {
  161. Vertex<V,E> from;
  162. Vertex<V,E> to;
  163. E weight;
  164. public Edge(Vertex<V, E> from, Vertex<V, E> to) {
  165. this.from = from;
  166. this.to = to;
  167. }
  168. /**
  169. * 判断两条边是否相同
  170. * @param obj
  171. * @return
  172. */
  173. @Override
  174. public boolean equals(Object obj) {
  175. Edge<V,E> edge=(Edge<V, E>) obj;
  176. return Objects.equals(from,edge.from) && Objects.equals(to,edge.to);
  177. }
  178. /**
  179. * 如果equals的结果是true说明一样,则hashcode值也一样
  180. * @return
  181. */
  182. @Override
  183. public int hashCode() {
  184. return from.hashCode()+to.hashCode();
  185. }
  186. }
  187. }

二、图的遍历

图的遍历

从图中某一顶点出发访问图中其余顶点,且每一个顶点仅被访问一次
图有2种常见的遍历方式(有向图、无向图都适用)

  • 广度优先搜索(Breadth First Search,BFS),又称为宽度优先搜索、横向优先搜索。
  • 深度优先搜索(Depth First Search,DFS)

发明“深度优先搜索”算法的2位科学家在1986年共同获得计算机领域的最高奖:图灵奖。

新的图接口

  1. package graph;
  2. import java.util.List;
  3. import java.util.Set;
  4. public interface Graph<V,E> {
  5. // 边的数量
  6. int edgesSize();
  7. // 顶点数量
  8. int verticesSize();
  9. // 添加顶点
  10. void addVertex(V v);
  11. // 添加边(无权值)
  12. void addEdge(V from,V to);
  13. // 添加边(有权值)
  14. void addEdge(V from,V to,E weight);
  15. // 删除顶点
  16. void removeVertex(V v);
  17. // 删除边
  18. void removeEdge(V from,V to);
  19. void print();
  20. void bfs(V begin, vertexVisitor<V> visitor); // 广度优先搜索
  21. /**
  22. * 非递归版的深度优先搜索
  23. * @param begin
  24. * @param visitor
  25. */
  26. void dfs(V begin, vertexVisitor<V> visitor);
  27. List<V> topologicalSort(); // 拓扑排序
  28. interface vertexVisitor<V>{
  29. boolean visit(V v);
  30. }
  31. }

三、广度优先搜索(BFS)

1.图解

之前所学的二叉树层序遍历就是一种广度优先搜索。

注:BFS结果不唯一

经历一层可以访问到的

 2.思路

将该点从队列取出来,然后再将其所子节点依次放入队列

从某个点开始,将它可以到达的点放入队列,如果已经访问过则跳过,然后从队列中取出点重复该过程。

第一层:假设从点A开始,它可以到达B、F,则将B、F入队。
此时队列中元素 [B、F]
第二层:队头B出队,B可以到达C、I、G,将C、I、G入队。
此时队列中元素 [F、C、I、G]
第三层:队头F出队,F可以到达G、E,但G已访问过,将E入队。
此时队列中元素 [C、I、G、E]
第四层:队头C出队,C可以到达I、D,但I已访问过,将D入队。
此时队列中元素 [I、G、E、D]
第五层:队头I出队,I可以到达D,但D已访问过,不执行操作。
此时队列中元素 [G、E、D]
第六层:队头G出队,G可以到达D、H,但D已访问过,将H入队。
此时队列中元素 [E、D、H]
第七层:队头E出队,E可以到达D、H、F,都访问过,不执行操作。
此时队列中元素 [D、H]
第八层:队头D出队,D可以到达C、H、E,都访问过,不执行操作。
此时队列中元素 [H]
第九层:队头H出队,H可以到达D、G、E,都访问过,不执行操作。
此时队列中元素 []
队列为空,广度优先搜索结束。

3.代码实现

  1. /**
  2. * 广度优先搜索:BFS
  3. * @param begin
  4. * @param visitor
  5. */
  6. @Override
  7. public void bfs(V begin, vertexVisitor<V> visitor) {
  8. if (visitor ==null) return;
  9. //根据传入的值begin找到顶点
  10. Vertex<V,E> beginVertex=vertices.get(begin);
  11. //该顶点不存在,不做操作
  12. if (beginVertex==null) return;
  13. //存放已经访问过的节点
  14. //因为如果不存放,则遍历到下一个顶点的时候,会将刚刚出队列的数值再一次放入
  15. Set<Vertex<V,E>> visitedVertices=new HashSet<>();
  16. //临时存放
  17. Queue<Vertex<V,E>> queue=new LinkedList<>();
  18. queue.offer(beginVertex); //元素入队
  19. //将已经加入过队列的元素标记一下
  20. visitedVertices.add(beginVertex);
  21. //思考参考二叉树层次遍历,队列存放每一层的顶点,用集合记录已经访问过的点
  22. while (!queue.isEmpty()){
  23. //从队列中取出第一个顶点
  24. Vertex<V,E> vertex=queue.poll();
  25. if (visitor.visit(vertex.value)) return;
  26. //遍历【从队列中取出的顶点】的出去的边,将【这些边的终点】入队,并且标记为已经访问过
  27. for (Edge<V,E> edge:vertex.outEdges){
  28. //如果集合中已经记录该顶点,说明已经访问过,跳过进行下一轮
  29. if (visitedVertices.contains(edge.to)) continue;
  30. queue.offer(edge.to);
  31. visitedVertices.add(edge.to);
  32. }
  33. }
  34. }

四、深度优先搜索(DFS)

之前所学的二叉树前序遍历就是一种深度优先搜索。

注:DFS结果不唯一。

1.前序遍历

 2.图解

 

3.递归实现

  1. /**
  2. * 递归实现--->深度优先遍历:DFS
  3. * @param begin
  4. * @param visitedVertices 已经访问过的节点
  5. */
  6. /**
  7. * 递归实现深度优先搜索DFS
  8. */
  9. public void dfs(V begin) {
  10. Vertex<V, E> beginVertex = vertices.get(begin); // 根据传入的值获取顶点
  11. if (beginVertex == null) return; // 顶点不存在则不执行操作
  12. dfs(beginVertex, new HashSet<>()); // 传入的集合,用来记录访问过的顶点
  13. }
  14. private void dfs(Vertex<V, E> vertex, Set<Vertex<V, E>> vistedVertices){
  15. System.out.println(vertex.value);
  16. vistedVertices.add(vertex);
  17. for(Edge<V, E> edge : vertex.outEdges){
  18. if(vistedVertices.contains(edge.to)) continue;
  19. //dfs会自己后退
  20. dfs(edge.to, vistedVertices);
  21. }
  22. }

 4.通过栈-----非递归实现

1)步骤

1.从outEdge中选择一条边

2.将选择边的from和to边,按顺序入栈【先入from再入to】

3.打印选择边的to

4.将to边添加到已经访问的范围中

5.break

2)图解

 3)代码实现

  1. /**
  2. * 非递归版(通过栈实现):深度优先搜索(DFS)
  3. * @param
  4. */
  5. @Override
  6. public void dfs(V begin, vertexVisitor<V> visitor) {
  7. if (visitor ==null) return;
  8. //查看该点是否存在
  9. Vertex<V,E> beginVertex=vertices.get(begin);
  10. if (begin==null) return;
  11. //创建一个set存放已经访问过的节点
  12. Set<Vertex<V,E>> visitedVertices=new HashSet<>();
  13. //创建一个栈临时存放
  14. Stack<Vertex<V,E>> stack=new Stack<>();
  15. //先访问起点
  16. stack.push(beginVertex);
  17. visitedVertices.add(beginVertex);
  18. //如果该节点已经访问过,则不再放入
  19. if (visitor.visit(begin)) return;
  20. while (!stack.isEmpty()){ //如果栈不为空
  21. Vertex<V,E> vertex=stack.pop();
  22. for (Edge<V,E> edge :vertex.outEdges){ //访问出发点的每一条边
  23. if (visitedVertices.contains(edge.to)) continue; //表示这条边已经访问过
  24. //找了一条适合且未遍历过的边
  25. stack.push(edge.from);//将出发点加入栈中
  26. stack.push(edge.to);//将出度点加入栈中
  27. //将加入栈中的节点加入已经访问过发范围中
  28. visitedVertices.add(edge.to);
  29. if (visitor.visit(edge.to.value)) return;
  30. break;
  31. }
  32. }
  33. }

五、AOV网(Activity On Vertex Network)

一项大的工程常被分为多个小的子工

子工程之间可能存在一定的先后顺序,即某些子工程必须在其他的一些子工程完成后才能开始。
在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,子工程被称为活动(Activity)

以顶点表示活动、有向边表示活动之间的先后关系,这样的图简称为 AOV 网。【有很强的依赖关系】
标准的AOV网必须是一个有向无环图(Directed Acyclic Graph,简称 DAG)

六、拓扑排序(Topological Sort) 

1.简介

前驱活动:有向边起点的活动称为终点的前驱活动

  • 只有当一个活动的前驱全部都完成后,这个活动才能进行

后继活动:有向边终点的活动称为起点的后继活动

 2.思路

可以使用卡恩算法(Kahn于1962年提出)完成拓扑排序。

假设 L 是存放拓扑排序结果的列表:

  • ① 把所有入度为 0 的顶点放入 queue 中,然后把这些顶点从图中去掉,放入list中
  • ② 然后找到已经从queue中移除的入度为0的节点的下一个节点,将下一个节点的入度-1,如果-1后,入度为0,则直接加入queue
  • ③ 重复操作 ①,直到找不到入度为 0 的顶点
  • 如果此时 L 中的元素个数和顶点总数相同,说明拓扑排序完成
  • 如果此时 L 中的元素个数少于顶点总数,说明原图中存在环,无法进行拓扑排序

3.代码实现

  1. /**
  2. * 拓扑排序
  3. * @return
  4. */
  5. @Override
  6. public List<V> topologicalSort() {
  7. List<V> list = new ArrayList<>();
  8. Queue<Vertex<V, E>> queue = new LinkedList<>();
  9. Map<Vertex<V, E>, Integer> ins = new HashMap<>();
  10. // 初始化(将度为0的节点放入队列)
  11. vertices.forEach((V v, Vertex<V, E> vertex) -> {
  12. int indegree = vertex.inEdges.size(); // 入度
  13. if(indegree == 0) { // 入度为0,放入队列
  14. queue.offer(vertex);
  15. } else { // 入度不为0,用map记录它的入度
  16. ins.put(vertex, indegree);
  17. }
  18. });
  19. while(!queue.isEmpty()){ // 从队列中取节点
  20. //取出队列中的第一个元素
  21. Vertex<V, E> vertex = queue.poll();
  22. list.add(vertex.value); // 放入返回结果中
  23. //遍历取出的节点的出度边
  24. for (Edge<V, E> edge : vertex.outEdges){
  25. // 队列中取出节点所通向节点的入度
  26. int toIndegree = ins.get(edge.to) - 1;
  27. if(toIndegree == 0) { // 入度为0,放入队列
  28. queue.offer(edge.to);
  29. } else { // 入度不为0,用map记录它的入度
  30. ins.put(edge.to, toIndegree);
  31. }
  32. }
  33. }
  34. return list;
  35. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/870726
推荐阅读
相关标签
  

闽ICP备14008679号