持续学习&持续更新中…

学习态度:脚踏实地


【恋上数据结构与算法 第二季】【04】图-基础实现_遍历_拓扑排序

  • 图的实现方案
  • 邻接矩阵
  • 邻接表
  • 图的基础接口
  • 顶点、边的定义
  • 图的基础实现
  • 图的遍历
  • 广度优先搜索
  • 深度优先搜索
  • 修改遍历接口
  • AOV网
  • 拓扑排序
  • 参考

代码实现图时并没有采用传统的邻接矩阵或者邻接表,因为它们都太复杂、太麻烦了。
代码实现采用了一种比较折中的方案,比较偏向于邻接表。

代码实现图时,一般实现为有向图,因为无向图可以用有向图来表达。

图的实现方案

邻接矩阵

邻接表

邻接表只需要一个一维数组

图的基础接口

public interface Graph<V, E> {int vertexSize(); // 顶点的数量int edgeSize(); // 边的数量void addVertex(V v); // 添加一个顶点void removeVertex(V v); // 删除一个顶点void addEdge(V from, V to); // 添加一条边void addEdge(V from, V to, E weight); // 添加一条边(带权值)void removeEdge(V from, V to); // 删除一条边
}

顶点、边的定义

public class ListGraph<V, E> implements Graph<V, E> {// 顶点private static class Vertex<V, E> {V value; // 顶点存储的元素Set<Edge<V, E>> inEdges = new HashSet<>(); // 以该顶点为终点的边(到达该顶点的边)Set<Edge<V, E>> outEdges = new HashSet<>(); // 以该顶点为起点的边(从该顶点出发的边)Vertex(V value) {this.value = value;}@Overridepublic boolean equals(Object o) {Vertex<V, E> vertex = (Vertex<V, E>) o;return Objects.equals(value, vertex.value);}@Overridepublic int hashCode() {return value != null ? value.hashCode() : 0;}@Overridepublic String toString() {return value == null ? "null" : value.toString();}}// 边private static class Edge<V, E> {E weight; // 边的权值Vertex<V, E> from; // 这条边从哪个顶点出发Vertex<V, E> to; // 这条边要到达哪个顶点Edge(Vertex<V, E> from, Vertex<V, E> to, E weight) {this.from = from;this.to = to;this.weight = weight;}@Overridepublic boolean equals(Object o) {Edge<V, E> edge = (Edge<V, E>) o;return from.equals(edge.from) && to.equals(edge.to);}@Overridepublic int hashCode() {int result = 0;result = 31 * result + from.hashCode();result = 31 * result + to.hashCode();return result;}@Overridepublic String toString() {return "Edge{" +"from=" + from +", to=" + to +", weight=" + weight +'}';}}
}

图的基础实现

public class ListGraph<V, E> implements Graph<V, E> {private final Map<V, Vertex<V, E>> vertices = new HashMap<>();private final Set<Edge<V, E>> edges = new HashSet<>();public void print() {System.out.println("顶点:");vertices.forEach((k, v) -> {System.out.println(k);System.out.println("in:");System.out.println(v.inEdges);System.out.println("out:");System.out.println(v.outEdges);System.out.println("---------------------");});System.out.println("边:");edges.forEach(System.out::println);}@Overridepublic int vertexSize() {return vertices.size();}@Overridepublic int edgeSize() {return edges.size();}@Overridepublic void addVertex(V v) {if (vertices.containsKey(v)) return;vertices.put(v, new Vertex<>(v));}@Overridepublic void removeVertex(V v) {//        Vertex<V, E> vertex = vertices.get(v);
//        if (null == vertex) return;final Vertex<V, E> removeVertex = vertices.remove(v);if (null == removeVertex) return;for (Iterator<Edge<V, E>> iterator = removeVertex.outEdges.iterator(); iterator.hasNext(); ) {final Edge<V, E> edge = iterator.next();edges.remove(edge);edge.to.inEdges.remove(edge);
//            iterator.remove();}
//        removeVertex.outEdges.clear();removeVertex.outEdges = null;for (Iterator<Edge<V, E>> iterator = removeVertex.inEdges.iterator(); iterator.hasNext(); ) {final Edge<V, E> edge = iterator.next();edges.remove(edge);edge.from.outEdges.remove(edge);
//            iterator.remove();}
//        removeVertex.inEdges.clear();removeVertex.inEdges = null;}@Overridepublic void addEdge(V from, V to) {addEdge(from, to, null);}// 如果发现某个顶点不存在那么需要创建该顶点@Overridepublic void addEdge(V from, V to, E weight) {Vertex<V, E> fromVertex = vertices.get(from);Vertex<V, E> toVertex = vertices.get(to);if (fromVertex == null) {fromVertex = new Vertex<>(from);vertices.put(from, fromVertex);}if (toVertex == null) {toVertex = new Vertex<>(to);vertices.put(to, toVertex);}Edge<V, E> edge = new Edge<>(fromVertex, toVertex, weight);if (fromVertex.outEdges.remove(edge)) { // 如果已经存在这条边了toVertex.inEdges.remove(edge);edges.remove(edge);}fromVertex.outEdges.add(edge);toVertex.inEdges.add(edge);edges.add(edge);}@Overridepublic void removeEdge(V from, V to) {Vertex<V, E> fromVertex = vertices.get(from);Vertex<V, E> toVertex = vertices.get(to);if (fromVertex == null || toVertex == null) {return;}Edge<V, E> edge = new Edge<>(fromVertex, toVertex, null);if (edges.remove(edge)) { // 如果存在这条边的话,才需要删除toVertex.inEdges.remove(edge);fromVertex.outEdges.remove(edge);}}// 顶点private static class Vertex<V, E> {V value; // 顶点存储的元素Set<Edge<V, E>> inEdges = new HashSet<>(); // 以该顶点为终点的边(到达该顶点的边)Set<Edge<V, E>> outEdges = new HashSet<>(); // 以该顶点为起点的边(从该顶点出发的边)Vertex(V value) {this.value = value;}@Overridepublic boolean equals(Object o) {Vertex<V, E> vertex = (Vertex<V, E>) o;return Objects.equals(value, vertex.value);}@Overridepublic int hashCode() {return value != null ? value.hashCode() : 0;}@Overridepublic String toString() {return value == null ? "null" : value.toString();}}// 边private static class Edge<V, E> {E weight; // 边的权值Vertex<V, E> from; // 这条边从哪个顶点出发Vertex<V, E> to; // 这条边要到达哪个顶点Edge(Vertex<V, E> from, Vertex<V, E> to, E weight) {this.from = from;this.to = to;this.weight = weight;}@Overridepublic boolean equals(Object o) {Edge<V, E> edge = (Edge<V, E>) o;return from.equals(edge.from) && to.equals(edge.to);}@Overridepublic int hashCode() {int result = 0;result = 31 * result + from.hashCode();result = 31 * result + to.hashCode();return result;}@Overridepublic String toString() {return "Edge{" +"from=" + from +", to=" + to +", weight=" + weight +'}';}}
}

图的遍历

广度优先搜索

使用广度优先搜索遍历图时,从不同的顶点出发,遍历到的结果是不一样的(需要注意的是,有的顶点有时是遍历不到的)

    // 广度优先搜索@Overridepublic void bfs(V begin) {final Vertex<V, E> beginVertex = vertices.get(begin);if (null == beginVertex) return;Queue<Vertex<V, E>> queue = new LinkedList<>();Set<Vertex<V, E>> set = new HashSet<>();queue.offer(beginVertex);set.add(beginVertex);while (!queue.isEmpty()) {final Vertex<V, E> vertex = queue.poll();System.out.println(vertex); // 这里的遍历只是简单的打印一下顶点for (Edge<V, E> edge : vertex.outEdges) {if (set.contains(edge.to)) continue;queue.offer(edge.to);set.add(edge.to);}}}

深度优先搜索

使用深度优先搜索遍历图时,从不同的顶点出发,与广度优先搜索一样,遍历到的结果也会是不一样的(需要注意的是,有的顶点有时是遍历不到的);

使用深度优先搜索遍历图时,从相同的顶点出发进行遍历,也会有很多条路径可以走。

    // 深度优先搜索——递归实现@Overridepublic void dfs(V begin) {final Vertex<V, E> beginVertex = vertices.get(begin);if (null == beginVertex) return;dfs(beginVertex, new HashSet<>());}private void dfs(Vertex<V, E> vertex, Set<Vertex<V, E>> set) {System.out.println(vertex);set.add(vertex);for (Edge<V, E> edge : vertex.outEdges) {if (set.contains(edge.to)) continue;dfs(edge.to, set);}}

    // 深度优先搜索——非递归实现@Overridepublic void dfs(V begin) {final Vertex<V, E> beginVertex = vertices.get(begin);if (null == beginVertex) return;Set<Vertex<V, E>> set = new HashSet<>();Stack<Vertex<V, E>> stack = new Stack<>();stack.push(beginVertex);set.add(beginVertex);System.out.println(beginVertex);while (!stack.isEmpty()) {final Vertex<V, E> vertex = stack.pop();for (Edge<V, E> edge : vertex.outEdges) {if (set.contains(edge.to)) continue;stack.push(edge.from);stack.push(edge.to);set.add(edge.to);System.out.println(edge.to);break;}}}

修改遍历接口

public interface Graph<V, E> {int vertexSize(); // 顶点的数量int edgeSize(); // 边的数量void addVertex(V v); // 添加一个顶点void removeVertex(V v); // 删除一个顶点void addEdge(V from, V to); // 添加一条边 // 如果发现某个顶点不存在那么自动创建该顶点void addEdge(V from, V to, E weight); // 添加一条边(带权值) // 如果发现某个顶点不存在那么自动创建该顶点void removeEdge(V from, V to); // 删除一条边//    void bfs(V begin); // 广度优先搜索遍历
//    void dfs(V begin); // 深度优先搜索遍历void bfs(V begin, Visitor<V> visitor); // 广度优先搜索遍历void dfs(V begin, Visitor<V> visitor); // 深度优先搜索遍历interface Visitor<V> {void vertex(V v);}
}
    // 广度优先搜索@Overridepublic void bfs(V begin, Visitor<V> visitor) {if (null == visitor) return;final Vertex<V, E> beginVertex = vertices.get(begin);if (null == beginVertex) return;Queue<Vertex<V, E>> queue = new LinkedList<>();Set<Vertex<V, E>> set = new HashSet<>();queue.offer(beginVertex);set.add(beginVertex);while (!queue.isEmpty()) {final Vertex<V, E> vertex = queue.poll();visitor.vertex(vertex.value);for (Edge<V, E> edge : vertex.outEdges) {if (set.contains(edge.to)) continue;queue.offer(edge.to);set.add(edge.to);}}}// 深度优先搜索——非递归实现@Overridepublic void dfs(V begin, Visitor<V> visitor) {if (null == visitor) return;final Vertex<V, E> beginVertex = vertices.get(begin);if (null == beginVertex) return;Set<Vertex<V, E>> set = new HashSet<>();Stack<Vertex<V, E>> stack = new Stack<>();stack.push(beginVertex);set.add(beginVertex);visitor.vertex(beginVertex.value);while (!stack.isEmpty()) {final Vertex<V, E> vertex = stack.pop();for (Edge<V, E> edge : vertex.outEdges) {if (set.contains(edge.to)) continue;stack.push(edge.from);stack.push(edge.to);set.add(edge.to);visitor.vertex(edge.to.value);break;}}// 深度优先搜索——递归实现public void dfs2(V begin, Visitor<V> visitor) {if (null == visitor) return;final Vertex<V, E> beginVertex = vertices.get(begin);if (null == beginVertex) return;dfs2(beginVertex, new HashSet<>(), visitor);}private void dfs2(Vertex<V, E> vertex, Set<Vertex<V, E>> set, Visitor<V> visitor) {visitor.vertex(vertex.value);set.add(vertex);for (Edge<V, E> edge : vertex.outEdges) {if (set.contains(edge.to)) continue;dfs2(edge.to, set, visitor);}}}

如果想要在遍历的过程中可以停止(退出)遍历的话:

    void bfs(V begin, Visitor<V> visitor); // 广度优先搜索遍历void dfs(V begin, Visitor<V> visitor); // 深度优先搜索遍历
// 可以停止遍历interface Visitor<V> {boolean vertex(V v); // 返回true,就终止遍历}
// 可以停止遍历// 广度优先搜索@Overridepublic void bfs(V begin, Visitor<V> visitor) {if (null == visitor) return;final Vertex<V, E> beginVertex = vertices.get(begin);if (null == beginVertex) return;Queue<Vertex<V, E>> queue = new LinkedList<>();Set<Vertex<V, E>> set = new HashSet<>();queue.offer(beginVertex);set.add(beginVertex);while (!queue.isEmpty()) {final Vertex<V, E> vertex = queue.poll();if (visitor.vertex(vertex.value)) return;for (Edge<V, E> edge : vertex.outEdges) {if (set.contains(edge.to)) continue;queue.offer(edge.to);set.add(edge.to);}}}// 深度优先搜索——非递归实现@Overridepublic void dfs(V begin, Visitor<V> visitor) {if (null == visitor) return;final Vertex<V, E> beginVertex = vertices.get(begin);if (null == beginVertex) return;Set<Vertex<V, E>> set = new HashSet<>();Stack<Vertex<V, E>> stack = new Stack<>();stack.push(beginVertex);set.add(beginVertex);if (visitor.vertex(beginVertex.value)) return;while (!stack.isEmpty()) {final Vertex<V, E> vertex = stack.pop();for (Edge<V, E> edge : vertex.outEdges) {if (set.contains(edge.to)) continue;stack.push(edge.from);stack.push(edge.to);set.add(edge.to);if (visitor.vertex(edge.to.value)) return;break;}}}

AOV网

作业:自学《AOV网络》

拓扑排序

卡恩算法的思路是对的,但代码实现时,我们肯定不能将顶点删掉,因为这样会破坏原有的图结构,所以我们代码使用卡恩算法实现拓扑排序时,应该变通一下:

    // 使用卡恩算法对DAG进行拓扑排序@Overridepublic List<V> topologicalSort() {List<V> list = new ArrayList<>(); // list用来存放遍历结果,返回给使用者Queue<Vertex<V, E>> queue = new LinkedList<>(); // queue用来存放入度为0的顶点Map<Vertex<V, E>, Integer> map = new HashMap<>(); // 顶点的入度表// 初始化入度表和queue(将度为0的节点都放入队列)vertices.forEach((v, vertex) -> {int inSize = vertex.inEdges.size();if (inSize == 0) queue.offer(vertex);else// 初始化入度表时,没有必要将入度为0的顶点放入入度表中map.put(vertex, inSize);});while (!queue.isEmpty()) {Vertex<V, E> vertex = queue.poll();list.add(vertex.value);vertex.outEdges.forEach(edge -> {Integer integer = map.get(edge.to);integer--;if (integer == 0)// 将这个顶点入队后,就不用更新该顶点的入度了queue.offer(edge.to);else map.put(edge.to, integer);});}return list;}

测试:

public class Data {public static final Object[][] TOPO = {{0, 2},{1, 0},{2, 5}, {2, 6},{3, 1}, {3, 5}, {3, 7},{5, 7},{6, 4},{7, 6}};
}
    /*** 有向图*/private static Graph<Object, Double> directedGraph(Object[][] data) {Graph<Object, Double> graph = new ListGraph<>();for (Object[] edge : data) {if (edge.length == 1) {graph.addVertex(edge[0]);} else if (edge.length == 2) {graph.addEdge(edge[0], edge[1]);} else if (edge.length == 3) {double weight = Double.parseDouble(edge[2].toString());graph.addEdge(edge[0], edge[1], weight);}}return graph;}/*** 无向图*/private static Graph<Object, Double> undirectedGraph(Object[][] data) {Graph<Object, Double> graph = new ListGraph<>();for (Object[] edge : data) {if (edge.length == 1) {graph.addVertex(edge[0]);} else if (edge.length == 2) {graph.addEdge(edge[0], edge[1]);graph.addEdge(edge[1], edge[0]);} else if (edge.length == 3) {double weight = Double.parseDouble(edge[2].toString());graph.addEdge(edge[0], edge[1], weight);graph.addEdge(edge[1], edge[0], weight);}}return graph;}private static void testTopologicalSort() {Graph<Object, Double> graph = directedGraph(Data.TOPO);graph.topologicalSort().forEach(System.out::println);}public static void main(String[] args) {testTopologicalSort();}

参考

小码哥李明杰老师课程: 恋上数据结构与算法 第二季.


本文完,感谢您的关注支持!


【恋上数据结构与算法 第二季】【04】图-基础实现_遍历_拓扑排序相关推荐

  1. 《恋上数据结构与算法》第1季:算法概述

    数据结构与算法的学习笔记目录:<恋上数据结构与算法>的学习笔记 目录索引 算法概述 1. 算法和数据结构 1.1 什么是算法 1.2 什么是数据结构 2. 时间复杂度 2.1 如何判断一个 ...

  2. 如何有效学习《恋上数据结构与算法》,更快地理解数据代码?

    1.关于数据结构与算法? 数据结构就是为算法服务的,算法要作用在特定的数据结构之上.数据结构和算法相辅相成. 广义上讲就是 "操作一组数据的方法",像是你有很多个视频,我们怎么才能 ...

  3. MJ恋上数据结构(第1季 + 第2季)笔记

    文章转载自:https://blog.csdn.net/weixin_43734095/article/details/104847976 恋上数据结构完整笔记(第1季 + 第2季) 前言 数据结构 ...

  4. 数据结构与算法A实验六图论---7-5 任务调度的合理性(拓扑排序)

    假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行."任务调度"包括一组子任务.以及每个子任务可以执行所依赖的子任务集. 比如完 ...

  5. c++层次遍历_数据结构与算法,弄懂图的两种遍历方式

    1 引言   遍历是指从某个节点出发,按照一定的的搜索路线,依次访问对数据结构中的全部节点,且每个节点仅访问一次.  在二叉树基础中,介绍了对于树的遍历.树的遍历是指从根节点出发,按照一定的访问规则, ...

  6. 插入排序算法 及其二分搜索优化版 C++代码实现 恋上数据结构笔记

    复习梗概 文章目录 复习梗概 插入排序算法思想 插入排序时间复杂度与特性(多少,与什么有关?) 插入排序基础版 插入排序2nd优化版(优化了哪里?) !!!插入排序二分搜索优化版(优化了哪里?如何优化 ...

  7. 归并排序算法 C++实现与时间复杂度(考过)恋上数据结构笔记

    复习梗概 画图,自己整个数组,看代码写步骤,这个对理解归并排序还是很有必要的 合并两个有序数组的merge函数写法 时间复杂度的分析方法!!! 其实我觉得去b站找个动态的步骤分解视频也是不错的复习方法 ...

  8. 快速排序 C++代码实现及其算法思想及时间复杂度分析及优化 恋上数据结构笔记

    文章目录 复习梗概 算法思想 算法复杂度分析及稳定性 如何优化? 快速排序改进版代码C++ 快速排序个人青春版代码 完整代码 复习梗概 算法思想,别的排序名字直接就能让人联想到它的算法思想,唯独快速排 ...

  9. 《恋上数据结构第1季》二叉搜索树BST

    二叉搜索树(BinarySearchTree) BST 接口设计 BST 基础 添加元素: add() 删除元素: remove() 删除节点 – 叶子节点 删除节点 – 度为1的节点 删除节点 – ...

最新文章

  1. python使用matplotlib可视化雷达图(polar函数可视化雷达图、极坐标图、通过径向方向来显示数据之间的关系)
  2. luogu P5292 [HNOI2019]校园旅行
  3. vrml场景实例代码_高并发的中断下半部tasklet实例解析
  4. tcp连接的三次握手
  5. mysql ---- innodb-2-索引
  6. LSGO软件技术团队2015~2016学年第五周(0928~1004)总结
  7. C语言过时了?你在做梦?
  8. macos可以升级到指定版本吗_承装承修承试可以跨级升级吗?
  9. python保存rtmp流_ffmpeg 推送、保存rtmp 流命令
  10. 兰州职称计算机中心,【兰州2013年职称计算机考试报名通知】- 环球网校
  11. Java基础篇之返回值
  12. Linux在文件中查找the字样,Linux文件查找
  13. Google 工作十年后,我选择离开!
  14. 下载百度翻译英文读音
  15. SAT写作例子之Frank Lloyd Wright
  16. 堆内存和栈内存的区别
  17. rmd中将html转为pdf,Rmd文件转化为PDF报告
  18. office2007每次打开都配置进度_win7下office2007总是配置进度怎么办-解决office2007显示配置进度的方法 - 河东软件园...
  19. cad角度命令怎么输入_CAD阵列命令中角度阵列的使用技巧
  20. 三年程序员生涯的感悟、总结和憧憬

热门文章

  1. yii之PHPExcel导出
  2. dz调用图片PHP写法,100分discuz列表页调用帖子图片
  3. revit二次开发 获取楼板的边线/轮廓线
  4. 对于禁止U盘驱动的安装如何解除?
  5. Node.js笔记总结(day4)
  6. 解决Windows10、11 远程桌面时提示用户名、密码错误的情况(实际用户名、密码正确)
  7. python反爬虫应对措施之搭建代理IP池
  8. tp-link交换机 TL-SG1024T(T系列)三种模式的选择
  9. 主副路由器的两种方式
  10. 微信公众号测试号本地服务的搭建——Java