图代码实现

  • 图的基础代码
    • 顶点Vertex
    • 边Edge
    • 添加边addEdge
    • 删除边removeEdge
    • 删除点removeVertex
    • 完整源码
  • 图的遍历
    • 广度优先搜索(Breadth First Search)思路与实现
    • 深度优先搜索(Depth First Search)
      • 递归实现
      • 非递归思路与实现
  • AOV网(Activity On Vertex Network)
  • 拓扑排序(Topological Sort)
    • 拓扑排序 - 思路
    • 实现

图的基础代码

图的基础接口

package com.mj.graph;import java.util.List;public interface Graph<V, E> {int edgesSize();      // 边的数量int verticesSize();      // 顶点数量void addVertex(V v);         // 添加顶点void addEdge(V from, V to); // 添加边void addEdge(V from, V to, E weight);// 添加边void removeVertex(V v);         // 删除顶点void removeEdge(V from, V to);    // 删除边interface vertexVisitor<V>{boolean visit(V v);}}

顶点Vertex

/**
* 顶点*/
private static class Vertex<V, E> {V value;Set<Edge<V, E>> inEdges = new HashSet<>(); // 进来的边Set<Edge<V, E>> outEdges = new HashSet<>(); // 出去的边public Vertex(V value){this.value = value;}@Overridepublic boolean equals(Object obj) {return Objects.equals(value, ((Vertex<V, E>)obj).value);}@Overridepublic int hashCode() {return value == null ? 0 : value.hashCode();}@Overridepublic String toString() {return value == null ? "null" : value.toString();}}

边Edge

/** 边*/
private static class Edge<V, E> {Vertex<V, E> from; // 出发点Vertex<V, E> to; // 到达点E weight;    // 权值public Edge(Vertex<V, E> from, Vertex<V, E> to) {this.from = from;this.to = to;}@Overridepublic boolean equals(Object obj) {Edge<V, E> edge = (Edge<V, E>) obj;return Objects.equals(from, edge.from) && Objects.equals(to, edge.to);}@Overridepublic int hashCode() {return from.hashCode() * 31 + to.hashCode();}@Overridepublic String toString() {return "Edge [from=" + from + ", to=" + to + ", weight=" + weight + "]";}}

一些稍微复杂的操作单独列出来,简单地操作直接从完整源码中查看即可。

添加边addEdge

/*** 添加无权值的边*/
@Override
public void addEdge(V from, V to) {addEdge(from, to, null);
}/** 添加有权值的边*/
@Override
public void addEdge(V from, V to, E weight) {// 根据传入的参数from找到出发点,如果不存在则创建Vertex<V, E> fromVertex = vertices.get(from);if(fromVertex == null){ fromVertex = new Vertex<>(from);vertices.put(from, fromVertex);}// 根据传入的参数to找到终点,如果不存在则创建Vertex<V, E> toVertex = vertices.get(to);if(toVertex == null){toVertex = new Vertex<>(to);vertices.put(to, toVertex);}// 根据出发点与终点,创建边Edge<V, E> edge = new Edge<>(fromVertex, toVertex);edge.weight = weight; // 有权值则加上权值,无权值则为null// 不管原来是否存在,都先删除此边,再添加进去if(fromVertex.outEdges.remove(edge)){ toVertex.inEdges.remove(edge);edges.remove(edge);}fromVertex.outEdges.add(edge);toVertex.inEdges.add(edge);edges.add(edge);
}

删除边removeEdge

/** 删除边*/
@Override
public void removeEdge(V from, V to) {// 根据传入的from获得起点,不存在则不需要删除Vertex<V, E> fromVertex = vertices.get(from);if(fromVertex == null) return;// 根据传入的to找到终点,不存在则不需要删除Vertex<V, E> toVertex = vertices.get(to);if(toVertex == null) return;// 根据起点和终点获得边,然后删除Edge<V, E> edge = new Edge<>(fromVertex, toVertex);if(fromVertex.outEdges.remove(edge)){toVertex.inEdges.remove(edge);edges.remove(edge);}
}

删除点removeVertex

/** 删除点*/
@Override
public void removeVertex(V v) {// 根据传入的值找到点并删除,不存在则不做操作Vertex<V, E> vertex = vertices.remove(v);if(vertex == null) return;// 迭代器遍历集合vertex.outEdges, 删除所有从该点出去的边for (Iterator<Edge<V, E>> iterator = vertex.outEdges.iterator(); iterator.hasNext();) {Edge<V, E> edge = iterator.next(); // 遍历到的该点出去的边edge.to.inEdges.remove(edge);// 获取终点进入的边,并从中删除遍历到的边iterator.remove(); // 将当前遍历到的元素edge从集合vertex.outEdges中删掉edges.remove(edge);}// 迭代器遍历集合vertex.inEdges, 删除所有进入该点的边for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {Edge<V, E> edge = iterator.next(); // 遍历到的进入该点的边edge.from.outEdges.remove(edge); // 获取起点出去的边,并从中删除遍历到的边iterator.remove(); // 将当前遍历到的元素edge从集合vertex.inEdges中删掉edges.remove(edge);}}

完整源码

/*** 邻接表实现图*/
@SuppressWarnings("unchecked")
public class ListGraph<V, E> implements Graph<V, E> {// 传入的V与顶点类Vertex的映射private Map<V, Vertex<V, E>> vertices = new HashMap<>();// 边的Set集合private Set<Edge<V, E>> edges = new HashSet<>();/*** 顶点*/private static class Vertex<V, E> {V value;Set<Edge<V, E>> inEdges = new HashSet<>(); // 进来的边Set<Edge<V, E>> outEdges = new HashSet<>(); // 出去的边public Vertex(V value){this.value = value;}@Overridepublic boolean equals(Object obj) {return Objects.equals(value, ((Vertex<V, E>)obj).value);}@Overridepublic int hashCode() {return value == null ? 0 : value.hashCode();}@Overridepublic String toString() {return value == null ? "null" : value.toString();}}/*** 边*/private static class Edge<V, E> {Vertex<V, E> from; // 出发点Vertex<V, E> to; // 到达点E weight;   // 权值public Edge(Vertex<V, E> from, Vertex<V, E> to) {this.from = from;this.to = to;}@Overridepublic boolean equals(Object obj) {Edge<V, E> edge = (Edge<V, E>) obj;return Objects.equals(from, edge.from) && Objects.equals(to, edge.to);}@Overridepublic int hashCode() {return from.hashCode() * 31 + to.hashCode();}@Overridepublic String toString() {return "Edge [from=" + from + ", to=" + to + ", weight=" + weight + "]";}}public void print(){System.out.println("[顶点]-------------------");vertices.forEach((V v, Vertex<V, E> vertex) -> {System.out.println(v);System.out.println("out-----------");System.out.println(vertex.outEdges);System.out.println("int-----------");System.out.println(vertex.inEdges);});System.out.println("[边]-------------------");edges.forEach((Edge<V, E> edge) -> {System.out.println(edge);});}@Overridepublic int edgesSize() {return edges.size();}@Overridepublic int verticesSize() {return vertices.size();}@Overridepublic void addVertex(V v) {if(vertices.containsKey(v)) return;vertices.put(v, new Vertex<>(v));}@Overridepublic void addEdge(V from, V to) {addEdge(from, to, null);}@Overridepublic void addEdge(V from, V to, E weight) {// 根据传入的参数from找到起点,如果不存在则创建Vertex<V, E> fromVertex = vertices.get(from);if(fromVertex == null){ fromVertex = new Vertex<>(from);vertices.put(from, fromVertex);}// 根据传入的参数to找到终点,如果不存在则创建Vertex<V, E> toVertex = vertices.get(to);if(toVertex == null){toVertex = new Vertex<>(to);vertices.put(to, toVertex);}// 根据出发点与终点,创建边Edge<V, E> edge = new Edge<>(fromVertex, toVertex);edge.weight = weight; // 有权值则加上权值,无权值则为null// 不管原来是否存在,都先删除此边,再添加进去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 removeVertex(V v) {// 根据传入的值找到点并删除,不存在则不做操作Vertex<V, E> vertex = vertices.remove(v);if(vertex == null) return;// 迭代器遍历集合vertex.outEdges, 删除所有从该点出去的边for (Iterator<Edge<V, E>> iterator = vertex.outEdges.iterator(); iterator.hasNext();) {Edge<V, E> edge = iterator.next(); // 遍历到的该点出去的边edge.to.inEdges.remove(edge);// 获取终点进入的边,并从中删除遍历到的边iterator.remove(); // 将当前遍历到的元素edge从集合vertex.outEdges中删掉edges.remove(edge);}// 迭代器遍历集合vertex.inEdges, 删除所有进入该点的边for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {Edge<V, E> edge = iterator.next(); // 遍历到的进入该点的边edge.from.outEdges.remove(edge); // 获取起点出去的边,并从中删除遍历到的边iterator.remove(); // 将当前遍历到的元素edge从集合vertex.inEdges中删掉edges.remove(edge);}}@Overridepublic void removeEdge(V from, V to) {// 根据传入的from获得起点,不存在则不需要删除Vertex<V, E> fromVertex = vertices.get(from);if(fromVertex == null) return;// 根据传入的to找到终点,不存在则不需要删除Vertex<V, E> toVertex = vertices.get(to);if(toVertex == null) return;// 根据起点和终点获得边,然后删除Edge<V, E> edge = new Edge<>(fromVertex, toVertex);if(fromVertex.outEdges.remove(edge)){toVertex.inEdges.remove(edge);edges.remove(edge);}}
}

图的遍历

图的遍历

  • 从图中某一顶点出发访问图中其余顶点,且每一个顶点仅被访问一次

图有2种常见的遍历方式(有向图、无向图都适用)

  • 广度优先搜索(Breadth First Search,BFS),又称为宽度优先搜索横向优先搜索
  • 深度优先搜索(Depth First Search,DFS)
    发明“深度优先搜索”算法的2位科学家在1986年共同获得计算机领域的最高奖:图灵奖。

在接口中增加 bfsdfs 方法。

package com.mj.graph;import java.util.List;public interface Graph<V, E> {int edgesSize(); // 边的数量int verticesSize();   // 顶点数量void addVertex(V v); // 添加顶点void addEdge(V from, V to); // 添加边void addEdge(V from, V to, E weight);// 添加边void removeVertex(V v); // 删除顶点void removeEdge(V from, V to); // 删除边void bfs(V begin, vertexVisitor<V> visitor); // 广度优先搜索void dfs(V begin, vertexVisitor<V> visitor); // 深度优先搜索List<V> topologicalSort(); // 拓扑排序interface vertexVisitor<V>{boolean visit(V v);}}

广度优先搜索(Breadth First Search)思路与实现

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

注:BFS结果不唯一


思路

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

  • 第一层:假设从点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,都访问过,不执行操作。
    此时队列中元素 []
  • 队列为空,广度优先搜索结束。

实现

/*** 广度优先搜索BFS*/
public void bfs(V begin, vertexVisitor<V> visitor) {if(visitor == null) return;// 根据传入的值begin找到顶点Vertex<V, E> beginVertex = vertices.get(begin);if(beginVertex == null) return; // 该顶点不存在,不做操作// 存放已经访问过的节点Set<Vertex<V, E>> visitedVertices = new HashSet<>();     Queue<Vertex<V, E>> queue = new LinkedList<>();queue.offer(beginVertex); // 元素入队visitedVertices.add(beginVertex);// 思路参考二叉树层次遍历,队列存放每一层的顶点,用集合记录已经访问过的点while(!queue.isEmpty()){Vertex<V, E> vertex = queue.poll(); // 队列中取出一个顶点if(visitor.visit(vertex.value)) return;// 遍历[队列中取出的顶点]的出去的边,将[这些边的终点]入队,并且标记为已经访问过for(Edge<V, E> edge : vertex.outEdges){// 如果集合中已经记录该顶点,说明已经访问过,跳过进行下一轮if(visitedVertices.contains(edge.to)) continue;queue.offer(edge.to);visitedVertices.add(edge.to);}}}

深度优先搜索(Depth First Search)

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

注:DFS结果不唯一。

递归实现

/*** 递归实现深度优先搜索DFS*/
public void dfs(V begin) {Vertex<V, E> beginVertex = vertices.get(begin); // 根据传入的值获取顶点if (beginVertex == null) return; // 顶点不存在则不执行操作dfs2(beginVertex, new HashSet<>()); // 传入的集合,用来记录访问过的顶点
}
private void dfs(Vertex<V, E> vertex, Set<Vertex<V, E>> vistedVertices){System.out.println(vertex.value);vistedVertices.add(vertex);for(Edge<V, E> edge : vertex.outEdges){if(vistedVertices.contains(edge.to)) continue;dfs2(edge.to, vistedVertices);}
}

非递归思路与实现

/*** 非递归实现深度优先搜索DFS*/
public void dfs(V begin, vertexVisitor<V> visitor){if(visitor == null) return;Vertex<V, E> beginVertex = vertices.get(begin);if(begin == null) return;Set<Vertex<V, E>> visitedVertices = new HashSet<>();Stack<Vertex<V, E>> stack = new Stack<>();stack.push(beginVertex); // 先访问起点visitedVertices.add(beginVertex);if(visitor.visit(begin)) return;while(!stack.isEmpty()){Vertex<V, E> vertex = stack.pop();for(Edge<V, E> edge : vertex.outEdges){if(visitedVertices.contains(edge.to)) continue;stack.push(edge.from);stack.push(edge.to);visitedVertices.add(edge.to);if(visitor.visit(edge.to.value)) return;break;}}
}

AOV网(Activity On Vertex Network)

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

  • 子工程之间可能存在一定的先后顺序,即某些子工程必须在其他的一些子工程完成后才能开始。

在现代化管理中,人们常用有向图描述和分析一项工程的计划和实施过程子工程被称为活动(Activity)

  • 顶点表示活动有向边表示活动之间的先后关系,这样的图简称为 AOV 网

标准的AOV网必须是一个有向无环图(Directed Acyclic Graph,简称 DAG)

拓扑排序(Topological Sort)

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

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

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

  • A 是 B 的前驱活动,B 是 A 的后继活动
  • B 是 C 的前驱活动,C 是 B 的后继活动

拓扑排序 - 思路

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

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

  • ① 把所有入度为 0 的顶点放入 L 中,然后把这些顶点从图中去掉
    ② 重复操作 ①,直到找不到入度为 0 的顶点
  • 如果此时 L 中的元素个数和顶点总数相同,说明拓扑排序完成
  • 如果此时 L 中的元素个数少于顶点总数,说明原图中存在环,无法进行拓扑排序

实现

/*** 拓扑排序*/
@Override
public List<V> topologicalSort() {List<V> list = new ArrayList<>();Queue<Vertex<V, E>> queue = new LinkedList<>();Map<Vertex<V, E>, Integer> ins = new HashMap<>();// 初始化(将度为0的节点放入队列)vertices.forEach((V v, Vertex<V, E> vertex) -> {int indegree = vertex.inEdges.size(); // 入度if(indegree == 0) { // 入度为0,放入队列queue.offer(vertex);} else { // 入度不为0,用map记录它的入度ins.put(vertex, indegree);}});    while(!queue.isEmpty()){ // 从队列中取节点Vertex<V, E> vertex = queue.poll();list.add(vertex.value); // 放入返回结果中for (Edge<V, E> edge : vertex.outEdges){// 队列中取出节点所通向节点的入度int toIndegree = ins.get(edge.to) - 1;if(toIndegree == 0) { // 入度为0,放入队列queue.offer(edge.to);} else { // 入度不为0,用map记录它的入度ins.put(edge.to, toIndegree);}}}return list;
}

【恋上数据结构】图代码实现、BFS、DFS、拓扑排序相关推荐

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

    持续学习&持续更新中- 学习态度:脚踏实地 [恋上数据结构与算法 第二季][04]图-基础实现_遍历_拓扑排序 图的实现方案 邻接矩阵 邻接表 图的基础接口 顶点.边的定义 图的基础实现 图的 ...

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

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

  3. 堆排序 C++代码实现及思想 排序过程输出 恋上数据结构笔记

    复习梗概 文章目录 复习梗概 什么是堆思想? 堆排序算法怎么来的? 什么是下滤?代码 什么是建堆?代码 堆排序本体 代码及排序过程输出 和时间复杂度 完整代码 什么是堆思想? 最大堆:树形结构,每一个 ...

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

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

  5. 基数排序及其思想 C++代码实现及分析 恋上数据结构笔记

    文章目录 复习梗概 算法思想 时间及空间复杂度 基数排序基础版代码 及输出结果 计数排序函数 基数排序函数 可视化输出 另一种思路 完整版代码 复习梗概 思想 如何取数字各个位位数 计数排序保证稳定性 ...

  6. 计数排序及其改进 C++代码实现与分析 恋上数据结构笔记

    文章目录 复习梗概 算法思想 基础思想 改进空间复杂度,改进不能对负数进行排序问题 改进稳定性 计数排序时间空间复杂度 计数排序基础版 代码及输出 计数排序第一次改进版 代码及输出 计数排序终极版 代 ...

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

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

  8. 《恋上数据结构第1季》二叉树代码实现

    二叉树(BinaryTree) BinaryTree 基础 遍历(先序.中序.后序.层次遍历) 先序遍历: preorder() 中序遍历: inorder() 后序遍历: postorder() 层 ...

  9. 【恋上数据结构】排序算法前置知识及代码环境准备

    排序准备工作 何为排序? 何为稳定性? 何为原地算法? 时间复杂度的知识 写排序算法前的准备 项目结构 Sort.java Asserts.java Integers.java Times.java ...

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

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

最新文章

  1. 这项技术是谷歌AI的New Sexy:利于隐私、节能环保,目前最大挑战是布道阐释
  2. Async/Await替代Promise的6个理由
  3. Flutter:使用 CustomClipper 绘制 N 角星
  4. ABAP 后台作业的一个状态查询工具
  5. 说到底企业是销售飞鸽传书2007
  6. 服务器主板点不亮排查
  7. 华为云FusionInsight MRS在金融行业存算分离的实践
  8. go并发编程-理解不同并发场景下的go原语
  9. linux c语言变量地址类型,C语言基础知识:访问内存地址的方法
  10. 极客c语言课程设计,c语言课程设计之实习报告共5天完整.doc
  11. linux统计某种文件大小命令,linux下对符合条件的文件大小做汇总统计的简单命令...
  12. php视频教程打包下载 - 网络上最好的php视频教程
  13. arduino下载库出错_纯干货!关于Arduino 库在多种操作系统安装使用最详细、最全面的指南及常见问题解决办法!...
  14. 5g无线图传信号测试软件,不到千元的5G无线图传?小试致迅CineEye
  15. view-source是一种协议,查看源码
  16. 使用面向对象方法实现猜拳游戏(Java)
  17. oracle 数据库模式对象,索引,序列,同义词,查看用户拥有的表,聚簇,数据库链接
  18. 裂变海报设计的落地干货,为什么海报在裂变活动中这么重要?
  19. 基于TSUNG对MQTT进行压力测试-测试结果
  20. 【Unity3D游戏开发】NGUI制作字体的三种方法 (二一)

热门文章

  1. 什么样的人适合做合伙人?
  2. 再谈重载:一个矢量类
  3. Mybatis_day3_Mybatis的多表查询
  4. 游标sql server_学习SQL:SQL Server游标
  5. 行存储索引改换成列存储索引_索引策略–第2部分–内存优化表和列存储索引
  6. Hyperledger Fabric 命令整理
  7. delphi FastReport 安装方法
  8. ie9 jscript7 内存不足 页面无响应
  9. LoadRunner接口工作总结
  10. mysql开启日志记录