图的深度优先遍历算法

在此介绍图的基本算法之一的深度优先遍历(DFS)算法
广度优先搜索(BFS).

什么是DFS

图是由节点(Node)和路径(Route)组成的一种数据结构,用于反应各节点间的关系。我们一般会使用这种数据结构来统计节点间的关系。
比如某个节点是否和其他所有节点有关联(不一定是直接关联)。这时,我们就需要根据图中的路径判断当前节点是否能够通过某条路径关联到其他所有的节点。
对于这种问题,通常会使用深度优先遍历(DFS)和广度优先遍历(BFS)两种遍历方式来解决。

深度优先遍历(DFS)指的是,从指定节点开始,找到一条路径一直向下搜索,直到搜索到的当前节点无法找到新的搜索路径(当前节点直接关联的节点均已搜索过),则返回上一级节点继续搜索其他路径,直到所有节点均已搜索到或所有路径均已搜索到。(说的通俗一点就是先一条路走到黑,不撞南墙不回头。撞了南墙再回头走其他路)

循序渐进

有如下有向图,我们的目标是统计判断从节点A出发是否能走到其他所有的节点

  • 首先构造图类的数据结构;
/**
- 数据结构 -- 图
- 包含节点和路径
*/
public class Graph {/*** 构造方法* @param routes 路径*/public Graph(List<Route> routes){if(this.rotes == null){this.rotes = new ArrayList<>();}if(this.nodes == null){this.nodes = new HashSet<>();}for(Route route : routes){this.nodes.add(route.getFrom());this.nodes.add(route.getTo());this.rotes.add(route);}}/*** 节点集合*/private Set<String> nodes;public Set<String> getNodes() {return nodes;}/*** 路径集合*/private List<Route> rotes;public List<Route> getRotes() {return rotes;}
}
/**
- 路径(有向图)
*/
public class Route {/*** 构造方法* @param from 开始节点* @param to 目标节点*/public Route(String from, String to){this.from = from;this.to = to;}/*** 开始节点*/private String from;public String getFrom() {return from;}/*** 目标节点*/private String to;public String getTo() {return to;}
}
  • 构造题目中图的对象;
   Graph graph = new Graph(new ArrayList<>(){{add(new Route("A", "B"));add(new Route("A", "C"));add(new Route("C", "B"));add(new Route("B", "E"));add(new Route("C", "D"));add(new Route("D", "F"));add(new Route("E", "F"));add(new Route("F", "G"));}});
  • 根据上述介绍,首先需要找到从A出发的一条路径;
   Route route = null;for(Route r : graph.getRotes()){if(r.getFrom().equals("A")){route = r;break;}}
  • 获取找到的路径的目标节点,从该节点出发继续搜索寻找路径;
   for(Route r : graph.getRotes()){if(r.getFrom().equals(route.getTo())){route = r;break;}}
  • 这里,我们发现每次寻找路径的处理逻辑是一致的,所以可以使用递归进行重构
   public void DFS(String node, Graph graph){Route route = null;for(Route r : graph.getRotes()){if(r.getFrom().equals(node)){route = r;break;}}if(route == null) return;DFS(route.getTo(), graph);}
  • 搜索完了这条路径(撞到南墙),可以判断从A可以到达B E F G,接下来需要回到上一级节点,再重新搜索其他路径。

  • 这时发现根据现在的代码逻辑,无法获取上级节点的其他路径了。这是由于之前找到了一条路径后就不继续找了,等我们回头再想找的时候,无法判断那些路径我们找过那些没有找过。知道了原因,就改成找到一条路径后不停止,将这条路径搜索完后接着找下一条路径。也就是将递归放到循环里面。

   public void DFS(String node, Graph graph){Route route = null;for(Route r : graph.getRotes()){if(r.getFrom().equals(node)){route = r;DFS(route.getTo(), graph);}}}
  • 发现变量route也不需要定义了,优化一下。
   public void DFS(String node, Graph graph){for(Route r : graph.getRotes()){if(!r.getFrom().equals(node)) continue;DFS(r.getTo(), graph);}}
  • 继续搜索,发现搜索到节点C的时候,找到了C到B的路径,然后又搜索到了B,第一条路径已经搜索过B的所有路径了,所以出现了重复搜索。这里就需要定义一个布尔型的列表来记录哪些节点已经搜索过,对于搜索过的节点,直接跳过。

  • 这里由于节点有自己的名字,所以使用Map来记录。

   public void DFS(String node, Graph graph, Map<String, Boolean> visited){// 将当前节点标记为已搜索visited.put(node, true);for(Route r : graph.getRotes()){if(!r.getFrom().equals(node)) continue;// 如果当前节点已搜索,则跳过if(visited.getOrDefault(route.getTo(), false)) continue;DFS(r.getTo(), graph, visited);}}
  • 搜索正常结束了,可以判断从A可以到其他所有节点,但是作为一种搜索算法,最好是能统计出来各节点的搜索顺序,这里用一个列表来顺序统计搜索到的节点。发现这个列表其实就是统计哪些节点已经搜索过了,也就是和之前的Map是同样的功能。所以可以不要Map了。
   public void DFS(String node, Graph graph, List<String> visitedNodes){// 记录访问的节点visitedNodes.add(node);for(Route route : graph.getRotes()){if(!route.getFrom().equals(node)) continue;// 节点已访问,则跳过if(visitedNodes.contains(route.getTo())) continue;DFS(route.getTo(), graph, visitedNodes);}}

到此,DFS深度优先遍历算法完成。
运行后可得到结果:A B E F G C D

进阶

不想使用递归方式进行深度优先搜索DFS的话,其实还可以使用栈来进行,代码如下:

  /*** 深度优先遍历(栈)** @param node 起始节点* @return 遍历结果的节点顺序列表*/public List<String> DFS(String node){List<String> resultList = new ArrayList<>();// 构建栈,加入其实节点Stack<String> stack = new Stack<>();stack.add(node);while(!stack.isEmpty()){// 将栈顶节点出栈并加入搜索结果列表String targetNode = stack.pop();resultList.add(targetNode);// 找到当前节点所能到达的节点并入栈for(Route route : graph.getRotes()){if(!route.getFrom().equals(targetNode)) continue;// 已加入栈或写入结果列表的节点表示已搜索过,跳过if(resultList.contains(route.getTo())|| stack.contains(route.getTo())) continue;stack.push(route.getTo());}}return resultList;}

执行后发现结果和之前不太一样,这是由于栈的先进后出原则,导致先找到的目标节点最后才进行搜索。如果想与之前结果一致,则可以再用一个栈缓存找到的节点,等找到所有的路径搜索完成后再将缓存栈中的节点以此加入元数据栈。

  /*** 深度优先遍历(栈)* @param node 起始节点* @param graph 目标图结构* @return 遍历结果的节点顺序列表*/public List<String> DFS(String node, Graph graph){List<String> resultList = new ArrayList<>();// 构建栈,加入起始节点Stack<String> stack = new Stack<>();stack.add(node);while(!stack.isEmpty()){// 将栈顶节点出栈并加入搜索结果列表String targetNode = stack.pop();resultList.add(targetNode);// 找到当前节点所能到达的节点并入栈Stack<String> tmpStack = new Stack<>();for(Route route : graph.getRotes()){if(!route.getFrom().equals(targetNode)) continue;// 已加入栈或写入结果列表的节点表示已搜索过,跳过if(resultList.contains(route.getTo())|| stack.contains(route.getTo())) continue;tmpStack.push(route.getTo());}while(!tmpStack.isEmpty()){stack.push(tmpStack.pop());}}return resultList;}

图的深度优先遍历DFS(JAVA)相关推荐

  1. 数据结构-图的深度优先遍历(DFS)和广度优先遍历(BFS)算法分析

    https://www.cnblogs.com/qzhc/p/10291430.html 最后一个广度优先有错误,H不指向E,只有G指向E,所以顺序应该是ABCFDHGE

  2. 图的深度优先遍历和广度优先遍历_图的深度优先遍历(DFS)与广度优先遍历(BFS)的c语言实现...

    头文件 #pragma warning( disable : 4996)#pragma once#ifndef _GRAPH_H_#define _GRAPH_H_ #define MAX_VERTE ...

  3. java数据结构和算法——图的深度优先(DFS)遍历

    目录 一.图的遍历介绍 二.图的深度优先搜索(Depth First Search) 三.图的深度优先遍历算法步骤 四.图的深度优先遍历示例需求 五.图的深度优先遍历代码示例 一.图的遍历介绍 所谓图 ...

  4. java语言实现图的深度优先遍历

    java语言实现图的深度优先遍历: 图的存储采用的是邻接矩阵存储的方式,对下面的无向图进行遍历 代码如下: public class Deep {int count=0;public static v ...

  5. 【图数据结构的遍历】java实现广度优先和深度优先遍历

    [图数据结构的遍历]java实现广度优先和深度优先遍历 宽度优先搜索(BFS)遍历图需要使用队列queue数据结构: 深度优先搜索(DFS, Depth First Search)的实现 需要使用到栈 ...

  6. 数据结构与算法(7-2)图的遍历(深度优先遍历DFS、广度优先遍历BFS)(分别用邻接矩阵和邻接表实现)

    目录 深度优先遍历(DFS)和广度优先遍历(BFS)原理 1.自己的原理图 2.官方原理图 一.邻接矩阵的深度优先遍历(DFS) 1.原理图 2. 过程: 3.总代码 二.邻接表的深度优先遍历(DFS ...

  7. 算法学习:图的深度优先遍历(DFS)

    图的深度优先遍历 采用邻接矩阵表示图的方法,递归实现. 栈的使用: 头文件:<stack> s.empty():栈空则返回true,否则返回false s.top():返回栈顶元素,不删除 ...

  8. Java实现深度优先遍历-DFS

    Java实现深度优先遍历-DFS 实现功能:使用深度优先遍历算法DFS计算地铁指定起点站和终点站之间的所有路径 1.构造地铁站点数据结构 地铁站点数据结构包含2个属性,1个站点名,1个邻接站点列表 i ...

  9. java随机生成迷宫(图的深度优先遍历)

    最近经常在机房看同学在玩一个走迷宫的游戏,比较有趣,自己也用java写一个实现随机生成迷宫的算法,其实就是一个图的深度优先遍历算法.基本思想就是,迷宫中的每个点都有四面墙,然后呢, 从任意一点开始访问 ...

最新文章

  1. C++中的yield和fork
  2. nginx搭建文件服务器脚本,nginx搭建web服务器,配置端口复用
  3. Unity3D对手机屏幕触摸的控制脚本
  4. 12.解决SUSE Linux无法使用SSH登录的问题
  5. Spring的核心机制依赖注入简介
  6. ofbiz中用 ajax 几点注意
  7. 线性代数学习之坐标转换和线性变换
  8. 实时渲染学习(十一)渲染加速算法总结
  9. dede后台登陆提示 验证码不正确 解决办法(新版)
  10. 保护水资源公益网站html,保护水资源公益广告词
  11. Hibernate框架检索策略
  12. [Go实战]简单使用scylladb
  13. 2019/9/1 ecam5
  14. 有人对你说辛苦了要怎么回复
  15. SprintBoot:Post请求的参数多一个逗号的解决方法
  16. AD18系统设置界面各层详解
  17. mysql zombodb_zombodb安装试用
  18. matlab计算连续复利,Code 2-1 单利、离散复利与连续复利的对比
  19. 初级软件测试工程师零基础入门指南
  20. Typora使用详解

热门文章

  1. go -- 指针和引用
  2. 牛刀小试(牛客小题)
  3. 开发工具HbuilderX的安装及使用
  4. ceph源码分析--Monitor paxos算法
  5. Ceph日常运维管理
  6. Silvaco TCAD仿真11——BJT结构仿真
  7. 2019新悦动打火困难解决了吗,全新悦动启动困难?
  8. CodeWar代码学习
  9. 生成Kindle可读的mobi和PDF电子书
  10. JavaScript 格式化数字(格式化为N位补0)