博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
并查集的应用之求解无向图中的连接分量个数
阅读量:6326 次
发布时间:2019-06-22

本文共 7974 字,大约阅读时间需要 26 分钟。

一,介绍

本文使用数据结构:并查集 来实现 求解无向图的连通分量个数。

无向图的连通分量就是:无向图的一个极大连通子图,在极大连通子图中任意两个顶点之间一定存在一条路径。对于连通的无向图而言,只有一个连通分量。

 

二,构造一个简单的无向图

这里仅演示求解无向图的连通分量,因此需要先构造一个无向图。图由顶点和边组成,并采用图的邻接表形式存储。顶点类和边类的定义如下:

1     private class Vertex{ 2         private Integer vertexLabel; 3         private List
adjEdges;//邻接表 4 public Vertex(Integer vertexLabel) { 5 this.vertexLabel = vertexLabel; 6 adjEdges = new LinkedList
(); 7 } 8 } 9 10 private class Edge{11 private Vertex endVertex;12 public Edge(Vertex v) {13 this.endVertex = v;14 }15 }

 

然后,再使用一个Map来存储图中的顶点。Map的Key为顶点的标识,Value为Vertex顶点对象。关于如何定义一个图,。

private Map
nondirectedGraph;

 

三,求解无向图的连通分量的思路

首先需要一个一维数组来存储并查集。这里一维数组的下标表示图的顶点标识,数组元素s[i]有两种表示含义:当数组元素大于0时,表示的是 顶点 i 的父结点位置 ;当数组元素s[i]小于0时,表示的是 顶点 i 为根的子树的高度(秩!)。从而将数组的下标与图的顶点一 一 对应起来。

private int[] s;    private int tree_numbers;//并查集中子树的棵数

 

求解连通的分量的总体思路如下:

①构造一个图。或者说得先有一个图

②根据图中的每一个顶点,初始化并查集。也就是对于每个顶点,构造一棵只有一个顶点的子树(并查集的子树)。

③对于图中的每一条边,这条边一定关联了两个顶点,检查这两个顶点是否在同一个子集合中,如果不在,则执行union操作将这两个顶点合并到同一个集合中

④当遍历完所有的边之后,并查集中子树的个数即为连通分量的个数

伪代码如下:

CONNECTED-COMPONENTS(G)    for each vertex v belongs to V(G)          do MAKE-SET(v)    for each edge(u,v) belongs to E(G)           do if FIND(u) != FIND(v)                then UNION(u,v)

 

四,代码分析及实现

关于并查集的理解参考这篇文章:

 为方便起见,这里假设顶点的标识从0开始的字符串类型的连续的数字,如 0,1,2,3,4.......

make_set方法初始化并查集

1     private void make_set(Map
graph){2 int size = graph.size();//顶点的个数3 s = new int[size];4 for(Vertex v : graph.values()){5 s[Integer.valueOf(v.vertexLabel)] = -1;//顶点的标识是从0开始连续的数字6 }7 8 tree_numbers = size;//初始时,一共有 |V| 个子树 9 }

s数组是并查集的存储结构,第2行获取图中顶点的个数,构造并查集数组。其中,数组的下标表示图中顶点的标识,数组元素s[i]有两种表示含义:当数组元素大于0时,表示的是 顶点 i 的父结点位置 ;当数组元素s[i]小于0时,表示的是 顶点 i 为根的子树的高度(秩!)

由于约定了顶点的标识为0,1,2,3.....故第5行根据图中每个顶点来构造一棵单节点的树。

假设无向图如下:顶点的标识为 0,1,2,3,4,5,6

构造初始并查集后,s数组内容如下:

 

union操作

1     private void union(int root1, int root2) { 2         if (find(root1) == find(root2)) 3             return; 4         //union中的参数是合并任意两个顶点,但是对于并查集,合并的对象是该顶点所在集合的代表顶点(根顶点) 5         root1 = find(root1);//查找顶点root1所在的子树的根 6         root2 = find(root2);//查找顶点root2所在的子树的根 7          8         if (s[root2] < s[root1])// root2 is deeper 9             s[root1] = root2;10         else {11             if (s[root1] == s[root2])// 一样高12                 s[root1]--;// 合并得到的新的子树高度增1 (以root1作为新的子树的根)13             s[root2] = root1;// root1 is deeper14         }15         tree_numbers--;// 合并后,子树的数目减少116     }

注意第5、6行。union操作的对象应该是子树的树根。因为,union时使用了按秩求并,使用的是子树的根结点的秩。

否则的话,程序将会有bug---求出错误的连通分量个数。

 

求解连通分量的代码如下:

1 public int connectedComponents(Map
graph) { 2 for (Vertex v : graph.values()) { 3 int startLabel = Integer.valueOf(v.vertexLabel); 4 5 List
edgeList = v.adjEdges; 6 for (Edge e : edgeList) { 7 Vertex end = e.endVertex;// 获得该边的终点 8 int endLabel = Integer.valueOf(end.vertexLabel); 9 10 if (find(startLabel) != find(endLabel))11 union(startLabel, endLabel);//这两个顶点不在同一个子树中,需要union12 }13 }14 return tree_numbers;15 }

 第5行,遍历图中的每一条边。第10行,对该边关联的两个顶点进行判断:这两个顶点是否已经连通了(在同一棵子树中了)

求解连通分量时,对图中的每个顶点和每条边都进行了一次遍历,故算法的时间复杂度为O(V+E)

 

五,整个完整代码

1 import java.util.LinkedHashMap;  2 import java.util.LinkedList;  3 import java.util.List;  4 import java.util.Map;  5   6 import c9.topo.FileUtil;  7   8 public class ConnectedComponents {  9     private class Vertex { 10         private String vertexLabel; 11         private List
adjEdges;// 邻接表 12 13 public Vertex(String vertexLabel) { 14 this.vertexLabel = vertexLabel; 15 adjEdges = new LinkedList
(); 16 } 17 } 18 19 private class Edge { 20 private Vertex endVertex; 21 22 public Edge(Vertex v) { 23 this.endVertex = v; 24 } 25 } 26 27 private Map
nonDirectedGraph; 28 29 public ConnectedComponents(String graphContent) { 30 nonDirectedGraph = new LinkedHashMap
(); 31 32 buildGraph(graphContent); 33 34 make_set(nonDirectedGraph);// 初始化并查集 35 } 36 37 private void buildGraph(String graphContent){ 38 String[] lines = graphContent.split("\n"); 39 40 String startNodeLabel, endNodeLabel; 41 Vertex startNode, endNode; 42 for(int i = 0; i < lines.length; i++){ 43 if(lines[i].length()==1)//某行只有一个顶点 44 { 45 startNodeLabel = lines[i]; 46 nonDirectedGraph.put(startNodeLabel, new Vertex(startNodeLabel)); 47 continue; 48 } 49 String[] nodesInfo = lines[i].split(","); 50 startNodeLabel = nodesInfo[0]; 51 endNodeLabel = nodesInfo[1]; 52 53 endNode = nonDirectedGraph.get(endNodeLabel); 54 if(endNode == null){ 55 endNode = new Vertex(endNodeLabel); 56 nonDirectedGraph.put(endNodeLabel, endNode); 57 } 58 59 startNode = nonDirectedGraph.get(startNodeLabel); 60 if(startNode == null){ 61 startNode = new Vertex(startNodeLabel); 62 nonDirectedGraph.put(startNodeLabel, startNode); 63 } 64 Edge e = new Edge(endNode); 65 //对于无向图而言,起点和终点都要添加边 66 endNode.adjEdges.add(e); 67 startNode.adjEdges.add(e); 68 } 69 } 70 71 private int[] s; 72 private int tree_numbers; 73 74 private void make_set(Map
graph) { 75 int size = graph.size(); 76 s = new int[size]; 77 for (Vertex v : graph.values()) { 78 s[Integer.valueOf(v.vertexLabel)] = -1;// 顶点的标识是从0开始连续的数字 79 } 80 81 tree_numbers = size;// 初始时,一共有 |V| 个子树 82 } 83 84 private void union(int root1, int root2) { 85 if (find(root1) == find(root2)) 86 return; 87 //union中的参数是合并任意两个顶点,但是对于并查集,合并的对象是该顶点所在集合的代表顶点(根顶点) 88 root1 = find(root1);//查找顶点root1所在的子树的根 89 root2 = find(root2);//查找顶点root2所在的子树的根 90 91 if (s[root2] < s[root1])// root2 is deeper 92 s[root1] = root2; 93 else { 94 if (s[root1] == s[root2])// 一样高 95 s[root1]--;// 合并得到的新的子树高度增1 (以root1作为新的子树的根) 96 s[root2] = root1;// root1 is deeper 97 } 98 tree_numbers--;// 合并后,子树的数目减少1 99 }100 101 private int find(int root) {102 if (s[root] < 0)103 return root;104 else105 return s[root] = find(s[root]);106 }107 108 public int connectedComponents(Map
graph) {109 for (Vertex v : graph.values()) {110 int startLabel = Integer.valueOf(v.vertexLabel);111 112 List
edgeList = v.adjEdges;113 for (Edge e : edgeList) {114 Vertex end = e.endVertex;// 获得该边的终点115 int endLabel = Integer.valueOf(end.vertexLabel);116 117 if (find(startLabel) != find(endLabel))118 union(startLabel, endLabel);119 }120 }121 return tree_numbers;122 }123 124 // for test purposes125 public static void main(String[] args) {126 String graphFilePath;127 if (args.length == 0)128 graphFilePath = "F:\\graph.txt";129 else130 graphFilePath = args[0];131 132 String graphContent = FileUtil.read(graphFilePath, null);// 从文件中读取图的数据133 ConnectedComponents cc = new ConnectedComponents(graphContent);134 int count = cc.connectedComponents(cc.nonDirectedGraph);135 System.out.println("连通分量个数:" + count);136 }137 }

FileUtil类中的完整代码实现。

 

六,测试

文件格式如下:

第一列表示起始顶点的标识,第二列表示终点的标识。若没有边,则一行中只有一个顶点。

生成的图如下:

整个程序运行完成后,并查集数组内容如下:

 

结果如下:

本文转自hapjin博客园博客,原文链接:http://www.cnblogs.com/hapjin/p/5486564.html,如需转载请自行联系原作者

你可能感兴趣的文章
ThinkPHP 3.2.x 集成极光推送指北
查看>>
MYSQL 表情评论存储(emoji)
查看>>
js作用域链
查看>>
java中如何选择Collection Class--java线程(第3版)
查看>>
ASP.NET页面之间传递值的几种方式
查看>>
Linux系统权限
查看>>
TinyTemplate模板引擎火热出炉,正式开源了~~~
查看>>
android开发之GPS定位详解
查看>>
Mac OS X如何重装 苹果电脑重装操作系统
查看>>
集算器读写EXCEL文件的代码示例
查看>>
Ubuntu Server上搭建可用于生产环境的ASP.NET服务器
查看>>
php---PHP使用GD库实现截屏
查看>>
华为交换机802.1x动态下发vlan配置
查看>>
spring boot websocket + thy模版
查看>>
查看文件的真实路径
查看>>
如何开发一个自己的 RubyGem?
查看>>
职工系统150206308
查看>>
『中级篇』K8S最小调度单位Pod(62)
查看>>
ACE网络编程思考(一)
查看>>
数据结构的几种存储方式
查看>>