编写算法求无向图的连通分量的个数_并查集的应用之求解无向图中的连接分量个数...

本文通过并查集数据结构,介绍如何求解无向图的连通分量个数。首先构造无向图,然后初始化并查集,接着遍历图中每条边,用并查集的union操作合并不在同一集合的顶点,最后并查集中子树的个数即为连通分量数量。文章提供了详细代码实现和测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,介绍

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

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

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

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

1 private classVertex{2 privateInteger vertexLabel;3 private List adjEdges;//邻接表

4 publicVertex(Integer vertexLabel) {5 this.vertexLabel =vertexLabel;6 adjEdges = new LinkedList();7 }8 }9

10 private classEdge{11 privateVertex endVertex;12 publicEdge(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)foreach vertex v belongs to V(G)do MAKE-SET(v)foreach 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(Mapgraph){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, introot2) {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 deeper

14 }15 tree_numbers--;//合并后,子树的数目减少1

16 }

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

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

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

1 public int connectedComponents(Mapgraph) {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 returntree_numbers;15 }

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

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

五,整个完整代码

1 importjava.util.LinkedHashMap;2 importjava.util.LinkedList;3 importjava.util.List;4 importjava.util.Map;5

6 importc9.topo.FileUtil;7

8 public classConnectedComponents {9 private classVertex {10 privateString vertexLabel;11 private List adjEdges;//邻接表

12

13 publicVertex(String vertexLabel) {14 this.vertexLabel =vertexLabel;15 adjEdges = new LinkedList();16 }17 }18

19 private classEdge {20 privateVertex endVertex;21

22 publicEdge(Vertex v) {23 this.endVertex =v;24 }25 }26

27 private MapnonDirectedGraph;28

29 publicConnectedComponents(String graphContent) {30 nonDirectedGraph = new LinkedHashMap();31

32 buildGraph(graphContent);33

34 make_set(nonDirectedGraph);//初始化并查集

35 }36

37 private voidbuildGraph(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, newVertex(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 = newVertex(endNodeLabel);56 nonDirectedGraph.put(endNodeLabel, endNode);57 }58

59 startNode =nonDirectedGraph.get(startNodeLabel);60 if(startNode == null){61 startNode = newVertex(startNodeLabel);62 nonDirectedGraph.put(startNodeLabel, startNode);63 }64 Edge e = newEdge(endNode);65 //对于无向图而言,起点和终点都要添加边

66 endNode.adjEdges.add(e);67 startNode.adjEdges.add(e);68 }69 }70

71 private int[] s;72 private inttree_numbers;73

74 private void make_set(Mapgraph) {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, introot2) {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(introot) {102 if (s[root] < 0)103 returnroot;104 else

105 return s[root] =find(s[root]);106 }107

108 public int connectedComponents(Mapgraph) {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 returntree_numbers;122 }123

124 //for test purposes

125 public static voidmain(String[] args) {126 String graphFilePath;127 if (args.length == 0)128 graphFilePath = "F:\\graph.txt";129 else

130 graphFilePath = args[0];131

132 String graphContent = FileUtil.read(graphFilePath, null);//从文件中读取图的数据

133 ConnectedComponents cc = newConnectedComponents(graphContent);134 int count =cc.connectedComponents(cc.nonDirectedGraph);135 System.out.println("连通分量个数:" +count);136 }137 }

FileUtil类可参考中的完整代码实现。

六,测试

文件格式如下:

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

生成的图如下:

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

结果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值