图算法系列之深度优先搜索

开发 架构 算法
在这篇采用的是union-find算法实现的连通性检查,本篇我们将采用深度优先搜索的方式来找出图中的所有连通分量

[[396433]]

本文转载自微信公众号「贝塔学JAVA」,作者Silently9527。转载本文请联系贝塔学JAVA公众号。

在上篇中我们学习了深度优先搜索,知道了如何通过深度优先搜索在图中寻找路径;本篇我们继续一起来学习深度优先搜索算法的其他应用场景

连通分量

从一幅图中找出所有的连通分量,这是也是深度优先搜索的一个应用场景。什么是连通分量?这个定义在之前的文章中已有提到《如何检测社交网络中两个人是否是朋友关系(union-find算法)》

在这篇采用的是union-find算法实现的连通性检查,本篇我们将采用深度优先搜索的方式来找出图中的所有连通分量

连通分量的API定义

  1. public class DepthFirstCC { 
  2.     public DepthFirstCC(Graph graph);  
  3.      
  4.     public boolean connected(int v, int w); //检查两个顶点是否连通 
  5.  
  6.     public int count(); //统计连通分量的总数 
  7.  
  8.     public int id(int v); //顶点v所在连通分量的标识 

连通分量的API实现

与之前一样没扫描到一个顶点我们就需要标记这个顶点,所以依然需要定义一个marked[]数组

为了统计出图中总共有多少连通分量,所以需要定义一个变量count

为了判断两个顶点是否相连,我们需要把相连的顶点对应的标识值记录成相同值,当在调用connected方法的时候直接取出两个顶点的标识值比较,如果相同就是连通的,否则就是非连通;

这个的标识值我们使用的是count的值,每个顶点都需要存一个标识值,所以还需要一个ids[]数组。

  1. public class DepthFirstCC { 
  2.     private boolean marked[]; 
  3.     private int count
  4.     private int[] ids; 
  5.  
  6.     public DepthFirstCC(Graph graph) { 
  7.         this.marked = new boolean[graph.V()]; 
  8.         this.ids = new int[graph.V()]; 
  9.  
  10.         for (int v = 0; v < graph.V(); v++) { 
  11.             if (!this.marked[v]) { 
  12.                 dfs(graph, v); 
  13.                 count++; 
  14.             } 
  15.         } 
  16.     } 
  17.  
  18.     private void dfs(Graph graph, int v) { 
  19.         this.marked[v] = true
  20.         this.ids[v] = count
  21.         for (int w : graph.adj(v)) { 
  22.             if (!this.marked[w]) { 
  23.                 dfs(graph, w); 
  24.             } 
  25.         } 
  26.     } 
  27.  
  28.     public boolean connected(int v, int w) { 
  29.         return id(v) == id(w); 
  30.     } 
  31.  
  32.     public int count() { 
  33.         return count
  34.     } 
  35.  
  36.     public int id(int v) { 
  37.         return ids[v]; 
  38.     } 
  39.  

单元测试

构造这样一个图,连通分量的总数应该是3

  1. @Test 
  2. public void test() { 
  3.     Graph graph = new Graph(10); 
  4.     graph.addEdge(0, 1); 
  5.     graph.addEdge(0, 2); 
  6.     graph.addEdge(0, 5); 
  7.     graph.addEdge(1, 3); 
  8.     graph.addEdge(2, 4); 
  9.     graph.addEdge(4, 3); 
  10.     graph.addEdge(5, 3); 
  11.  
  12.     graph.addEdge(6, 7); 
  13.  
  14.     graph.addEdge(8, 9); 
  15.  
  16.     DepthFirstCC cc = new DepthFirstCC(graph); 
  17.  
  18.     System.out.println(cc.connected(0,5)); 
  19.     System.out.println(cc.connected(1,2)); 
  20.  
  21.     System.out.println(cc.count()); 

基于深度优先搜索实现的连通性检查理论上说要比以前实现的union-find算法更快,因为检查连通性深度优先搜索实现的版本能够保证在常量时间内完成,而union-find算法不行;

但是union-find也有自己的优势: 不需要把完整的构造并表示一张图,更重要的是union-find算法是动态的添加节点。

检查无向图中是否有环

为了减小实现的复杂度,我们假设图中不存在自环和平行边;

假如从顶点v出发存在环,表示从顶点v出发的连通分量中某个顶点的邻接顶点是v,那么在搜索的过程中必定会再次遇到顶点v

实现的思路:

  1. 标记已经搜索过的每个顶点
  2. 当遇到了一个已经被标记过的顶点,表示已经图中存在环;
  3. 由于图是无向图,如果v-w相连,那么顶点v中的邻接表中有w,w邻接表中也会有v,但是他们没有构成环,所以需要排除掉该情况。
  1. public class Cycle { 
  2.     private boolean marked[]; 
  3.     private boolean hashCycle; 
  4.  
  5.     public Cycle(Graph graph) { 
  6.         this.marked = new boolean[graph.V()]; 
  7.         for (int s = 0; s < graph.V(); s++) { 
  8.             if (!this.marked[s]) { 
  9.                 dfs(graph, s, s); 
  10.             } 
  11.         } 
  12.     } 
  13.  
  14.     private void dfs(Graph graph, int v, int pV) { 
  15.         this.marked[v] = true
  16.         for (int w : graph.adj(v)) { 
  17.             if (!this.marked[w]) { 
  18.                 this.dfs(graph, w, v); 
  19.             } else if (w != pV) { 
  20.                 this.hashCycle = true
  21.                 return
  22.             } 
  23.         } 
  24.     } 
  25.  
  26.     public boolean hasCycle() { 
  27.         return hashCycle; 
  28.     } 

方法dfs的参数v表示需要待搜索的顶点,pV表示的是到达v的顶点,所以如果v的邻接表中有个顶点已被标记过并且该顶点不等于到达v的顶点,那么表示图中有环

检查无向图是否是二分图

何为二分图? 图中每条边所连接的顶点都属于不同的部分;如下图:

其中红色节点表示一个集合,白色节点是另一个集合,每条边连接的两个顶点属于不同的集合;

举个实际的例子就很好理解,电影与演员的关系,电影作为一个顶点,演员作为一个顶点,电影与电影直接是不会有边,演员与演员直接也不会有边,这就是一张二分图。

  1. public class TwoColorGraph { 
  2.     private boolean twoColor = true
  3.     private boolean[] marked; 
  4.     private boolean[] color; 
  5.  
  6.     public TwoColorGraph(Graph graph) { 
  7.         this.marked = new boolean[graph.V()]; 
  8.         this.color = new boolean[graph.V()]; 
  9.  
  10.         for (int v = 0; v < graph.V(); v++) { 
  11.             if (!this.marked[v]) { 
  12.                 dfs(graph, v); 
  13.             } 
  14.         } 
  15.     } 
  16.  
  17.     private void dfs(Graph graph, int v) { 
  18.         this.marked[v] = true
  19.         for (int w : graph.adj(v)) { 
  20.             if (!this.marked[w]) { 
  21.                 this.color[w] = !this.color[v]; 
  22.                 dfs(graph, w); 
  23.             } else if (this.color[w] == this.color[v]) { 
  24.                 this.twoColor = false
  25.                 return
  26.             } 
  27.         } 
  28.     } 
  29.  
  30.     public boolean isTwoColor() { 
  31.         return twoColor; 
  32.     } 

 

文中所有源码已放入到了github仓库:https://github.com/silently9527/JavaCore

 

责任编辑:武晓燕 来源: 贝塔学JAVA
相关推荐

2020-10-17 11:14:19

数据结构与算法系列

2023-04-14 08:07:20

数据结构算法搜索

2021-04-19 09:08:19

无向图数据结构

2021-05-10 08:07:40

图算法路径顶点

2020-09-16 12:23:37

TypeScript

2022-03-25 00:00:00

Splunk搜索SPL

2020-04-16 13:48:27

DFS BFS优先遍历

2017-03-20 13:09:33

Swift广度优先搜索手游开发

2018-04-04 10:19:32

深度学习

2014-08-13 11:04:02

搜索引擎排序算法

2018-03-26 20:07:25

深度学习

2020-12-16 05:58:43

算法数据结构前端

2023-11-06 07:46:22

图算法数据结构

2023-12-19 16:01:40

深度学习人工智能目标检测

2023-07-19 08:55:00

神经网络推荐系统

2021-04-16 11:31:24

人工智能深度学习

2009-02-25 13:59:57

布尔全文搜索全文搜索内置函数

2021-11-19 07:54:40

前端

2022-06-10 07:13:29

JVM垃圾回收

2021-10-11 06:38:52

递归二叉搜索树
点赞
收藏

51CTO技术栈公众号