持续学习&持续更新中…

学习态度:脚踏实地


【恋上数据结构与算法 第二季】【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. 数据蒋堂 | Hadoop - 一把杀鸡用的牛刀
  2. javamail.providers not found
  3. python中forward函数的引用_调用没有.forward()的forward函数
  4. 用ffmpeg把gif动图分离成多张图片和ffmpeg多张图片合成gif动图
  5. Cmder命令行工具在Windows系统中的配置
  6. win10磁盘检查命令
  7. 【Tiny4412】制作最小文件系统脚本
  8. 图论-欧拉图-欧拉回路-Euler-Fluery-Hierholzer-逐步插入回路法-DFS详解-并查集
  9. click点击后鼠标移去就失效怎么实现_鼠标右键失灵怎么办,你知道原因吗?
  10. SAP云解决方案和企业本地部署(On-Premise)混合架构下的安全认证权限管理
  11. 信息安全工程07875 自考软件工程 助学班复习纲要
  12. 51单片机带闹钟c语言程序,51单片机编写的闹钟程序
  13. Win10系统下基于Docker构建Appium容器连接Android模拟器Genymotion完成移动端Python自动化测试
  14. NCA: Neighbourhood Components Analysis
  15. 【复盘】互联网老辛七月复盘-年中flag
  16. 【Day2.4】在华欣葡萄酒庄园午餐,风景也值这个价钱
  17. 免费动态IP代理科普知识课堂—代理服务器的类型
  18. tomcatServlet
  19. Cannot create PoolableConnectionFactory (Access denied for user ''@'localhost' (using password: YES)
  20. oracle中timestamp怎么用,Oracle Timestamp类型

热门文章

  1. BZOJ 4883 [Lydsy2017年5月月赛]棋盘上的守卫(最小生成环套树森林)
  2. 大学生可以做的兼职有哪些?我收集了这份兼职指南,请查收
  3. a标签js阻止跳转_js阻止a标签href跳转的方法
  4. php 发送邮件端口,PHP 使用 PHPMailer 发送邮件
  5. Condition和 AQS 原理
  6. AMD三代锐龙箭在弦上:如此家族堪称豪华
  7. IIS脚本安装服务路径
  8. 为数据保驾护航,BIWIN佰维断电保护企业级SSD
  9. java实用案例教程_Java实用案例教程
  10. NPOI操作Excel常用函数