图算法系列之深度优先搜索(一)
吐血整理程序员必读书单:https://github.com/silently9527/ProgrammerBooks
微信公众号:贝塔学Java
前言
在上一篇中我们把图通过邻接表数组表示出来了,这个数据结构将会做我们实现图算法的基础,本篇我们将一起开始学习图算法的第一个搜索算法 - 深度优先搜索
搜索API的定义
public class Search { Search(Graph graph, int s); boolean marked(int v); int count(); }
在开始实现算法之前,我们依然先来定义搜索的API
- 构造方法提供了一个图对象,以及一个起点s,需要找到与s连通的所有顶点
- marked判断顶点s与v是否相邻
- count返回与顶点s相连的总顶点数
深度优先搜索
假如上图是一个迷宫,我们需要从顶点0开始找到一条出路,假设我们有一条无限长的绳子,和一支粉笔,那么可以这样考虑找到出路:
- 先选择一条通道走,在走的路上放上一根绳子
- 每遇到一个路口就用笔标记一下,继续选择一条未走过的通道
- 当遇到一个已经被标记的路口时就退回到上一个路口继续选择一个未走过的通道
- 当回退的路口已经没有路可以走的时候就在继续往后回退
这种方式绳子总能帮你找到一条出路,而标记不会让你重复走已经走过的通道。
深度优先搜索的实现思路就和走迷宫的方式一样;
public class DepthFirstSearch { private boolean marked[]; private int count; public DepthFirstSearch(Graph graph, int s) { this.marked = new boolean[graph.V()]; this.dfs(graph, s); } private void dfs(Graph graph, int v) { marked[v] = true; count++; for (int w : graph.adj(v)) { if (!marked[w]) { dfs(graph, w); } } } @Override public boolean marked(int v) { return marked[v]; } @Override public int count() { return count; } }
在搜索一张图的时候,使用递归来遍历所有的顶点,在访问其中一个顶点时:
- 标记它被已经访问
- 递归的访问与之相连的所有邻接点
单元测试:
构建下面这张图,然后测试深度优先搜索
@Test public void test() { Graph graph = new Graph(8); //构建一张图 graph.addEdge(0, 1); graph.addEdge(0, 2); graph.addEdge(0, 5); graph.addEdge(1, 3); graph.addEdge(2, 4); graph.addEdge(4, 3); graph.addEdge(5, 3); graph.addEdge(6, 7); //为了展示 SeDepthFirstSearcharch search = new DepthFirstSearch(graph, 0); System.out.println(search.count()); System.out.println(search.marked(6)); System.out.println(search.marked(7)); System.out.println(search.marked(2)); System.out.println(search.marked(5)); }
寻找路径的API
以上的递归算法只是一个开始,从上面的结果我们可以看出,我们只能判断出哪些顶点与起点s是连通的,无法给出具体的路径出来;换句话说,我们需要实现从顶点s到顶点v是否存在路径可达,如果存在请打印出来
public class Paths { Paths(Graph graph, int s); boolean hasPathTo(int v); //判断出从s->v是否存在路径 Iterable<Integer> pathTo(int v); //如果存在路径,返回路径 }
基于深度优先搜索查找图中的可达路径
我们依然基于这张图来看,由于我们需要找出可达的路径,所以我们在进行搜索的时候需要记录下图中的边,这里我们使用的是一个数组edgeTo[],如果存在一条边是v->w,那么可以表示成edgeTo[w]=v,在深度搜索完成之后这个edgeTo[]数组就是一颗由父链表示的一颗树
(父链树在之前的文章中也使用过《如何检测社交网络中两个人是否是朋友关系(union-find算法)》)
public class DepthFirstPaths { private boolean marked[]; private int[] edgeTo; private int s; DepthFirstPaths(Graph graph, int s) { this.s = s; this.marked = new boolean[graph.V()]; this.edgeTo = new int[graph.V()]; this.dfs(graph, s); } private void dfs(Graph graph, int v) { this.marked[v] = true; for (int w : graph.adj(v)) { if (!marked[w]) { this.edgeTo[w] = v; this.dfs(graph, w); } } } public boolean hasPathTo(int v) { return marked[v]; } public Iterable<Integer> pathTo(int v) { if (!hasPathTo(v)) { throw new IllegalStateException("s不能到达v"); } Stack<Integer> stack = new LinkedListStack<>(); stack.push(v); while (edgeTo[v] != s) { stack.push(edgeTo[v]); v = edgeTo[v]; } stack.push(s); return stack; } }
画图来详细跟踪深度优先搜索的运行轨迹,记录了edgeTo的变化以及父链树的逐渐形成
最终父链树形成了,接下来我们来写单元测试校验下生成的父链树和实际的运行结果是否一致
@Test public void test() { Graph graph = new Graph(8); graph.addEdge(0, 1); graph.addEdge(0, 2); graph.addEdge(0, 5); graph.addEdge(1, 3); graph.addEdge(2, 4); graph.addEdge(4, 3); graph.addEdge(5, 3); graph.addEdge(6, 7); DepthFirstPaths paths = new DepthFirstPaths(graph, 0); System.out.println(paths.hasPathTo(5)); System.out.println(paths.hasPathTo(2)); System.out.println(paths.hasPathTo(6)); paths.pathTo(5).forEach(System.out::print); System.out.println(); paths.pathTo(4).forEach(System.out::print); System.out.println(); paths.pathTo(2).forEach(System.out::print); }
验证结果完全匹配了父链树
文中所有源码已放入到了github仓库:
https://github.com/silently9527/JavaCore
最后(点关注,不迷路)
文中或许会存在或多或少的不足、错误之处,有建议或者意见也非常欢迎大家在评论交流。
最后,写作不易,请不要白嫖我哟,希望朋友们可以点赞评论关注三连,因为这些就是我分享的全部动力来源

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
【博客大赛】Java基础语法(七)——类和对象
文章目录 Java基础语法(七)——类和对象 一、类和对象的初步认识 二、 类和类的实例化 1.类的定义 2.实例化对象 3.如何使用类中的数据 三、 类的成员 1. 字段/属性/成员变量 (1)实例成员变量 (2)静态成员变量 2. 方法 (method) (1)实例成员方法 (2)静态成员方法 (3)toString方法 3. static 关键字 (1)修饰属性 (2)修饰方法 四、封装 (1)private实现封装 (2)getter and setter 方法 五、构造方法 (1)基本语法 (2)new 执行过程 (3)语法规则 (4)注意事项 (5) this关键字 this的引入 this的用法 六、认识代码块 1.静态代码块 2.实例代码块 3.执行顺序 七、补充 1.匿名对象 八、练习 九、总结 完! Java基础语法(七)——类和对象 接上篇博客——Java基础语法(六)——数组的定义与使用 本次内容介绍大纲 一、类和对象的初步认识 首先,类和对象是非常抽象的概念。我们要初步认识一下,什么是类?什么是对象? 类就是一类对象的统称。 对象就是这一类具体化的一个实例...
- 下一篇
白话运维监控系统-1.1 运维监控系统概述
写在前面 笔者从Open-Falcon开源到现在,从事运维监控领域相关工作差不多7年,在做Open-Falcon和Nightingale的社区答疑过程中发现,有大量的小白问题,很多是因为对这个领域缺乏基础认识,所以,想写一个针对入门级用户的系列文章,做一下知识的普及。 另外,监控这个事情,其实也是研发人员走到某个段位之后必须要了解的。因为监控是稳定性体系建设中最重要的一环,普通研发人员往架构师转变,需要了解更多横向的知识,比如持续集成、服务治理、稳定性保障等等,所以了解一下监控,也很有必要。 这是一个很公益的事情,希望大家一起参与讨论,分享经验,为小白领路,功德无量~ 监控的价值 监控是保障业务稳定性的重要手段,那怎么提升稳定性呢?简单来说,就是减少故障,一个是减少故障的数量,一个是减少单一故障的影响时长,即出现故障后快速止损。减少故障这个方面,更多的要诉诸于鲁棒的业务系统架构和稳定的基础设施,监控在这个方面没有办法提供太多助力。对于减少单一故障的影响时长,监控是非常有价值的。 在出现故障时,监控系统可以及时感知,及时发告警通知相关人员,让值班的人快速响应,处理故障。处理故障的第一步就...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- MySQL8.0.19开启GTID主从同步CentOS8
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Linux系统CentOS6、CentOS7手动修改IP地址
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Thymeleaf,官方推荐html解决方案