- 并查集是一种树型的数据结构,并查集可以高效的进行如下操作:
- 查询元素p和元素q是否属于同一组
- 合并元素p和元素q所在的组
1.1 并查集结构
- 并查集是一种树型结构,但这棵树和我们之前学的二叉树、红黑树、B树等都不一样,这种树的要求比较简单:
- 每个元素都唯一的对应一个结点
- 每一组数据中的多个元素都在同一棵树中
- 一个组中的数据对应的树和另外一个组中的数据对应的树之间没有任何联系
- 元素在树中并没有子父级关系的硬性要求
1.2 并查集API设计
1.3 并查集实现
1.3.1 UF(int N)构造方法实现
- 初始情况下,每个元素都在一个独立的分组中,所以,初始情况下,并查集中的数据默认分为N个组
- 初始化数组eleAndGroup
- 把eleAndGroup数组的索引看做是每个结点存储的元素,把eleAndGroup数组每个索引处的值看做是该结点所在的分组,那么初始化的情况下,i索引处存储的值就是i
1.3.2 union(int p, int q)合并方法实现
- 如果p和q已经在同一个分组中,则无需合并
- 如果p和q不在同一个分组,则只需要将p元素所在的组的所有的元素的组标识符修改为q元素所在组的标识符即可
- 分组数量-1
1.3.3 代码实现
package com.tiger.study.DataStructure.uf;
public class UF {
private int[] eleAndGroup;
private int count;
public UF(int N) {
this.count = N;
this.eleAndGroup = new int[N];
for (int i = 0; i < eleAndGroup.length; i++) {
eleAndGroup[i] = i;
}
}
public int count() {
return count;
}
public int find(int p) {
return eleAndGroup[p];
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
public void union(int p, int q) {
if (connected(p, q)) {
return;
}
int pGroup = find(p);
int qGroup = find(q);
for (int i = 0; i < eleAndGroup.length; i++) {
if (eleAndGroup[i] == pGroup) {
eleAndGroup[i] = qGroup;
}
}
this.count--;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
1.4 并查集应用举例
- 如果我们并查集存储的每一个整数表示的是一个大型计算机网络中的计算机,则我们就可以通过connected(int p,int q)来检测,该网络中的某两台计算机之间是否连通?如果连通,则他们之间可以通信,如果不连通,则不能通信,此时我们又可以调用union(int p,int q)使得p和q之间连通,这样两台计算机之间就可以通信了。
1.5 UF_Tree算法优化
- 为了提升union算法的性能,我们需要重新设计find方法和union方法的实现,此时我们先需要对我们之前数据结构中的eleAndGroup数组的含义进行重新设定
- 我们仍然让eleAndGroup数组的索引作为某个结点的元素
- eleAndGroup[i]的值不再是当前结点所在的分组标识,而是该节点的父结点
1.5.1 UF_Tree API设计
1.5.2 find(int p)查询方法实现
- 判断当前元素p的父结点eleAndGroup[p]是不是自己,如果是自己则证明已经是根结点了
- 如果当前元素p的父结点不是自己,则让p=eleAndGroup[p],继续找父结点的父结点直到找到根结点为止
1.5.3 union(int p, int q)合并方法实现
- 找到p元素所在树的根结点
- 找到q元素的根结点
- 如果p和q已经在同一个树中,则无需合并
- 如果p和q不在同一个分组,则只需要将p元素所在树根结点设置为q元素的根结点即可
- 分组数量-1
1.5.4 代码实现
package com.tiger.study.DataStructure.uf;
public class UF_Tree {
private int[] eleAndGroup;
private int count;
public UF_Tree(int N) {
this.count = N;
this.count = N;
this.eleAndGroup = new int[N];
for (int i = 0; i < eleAndGroup.length; i++) {
eleAndGroup[i] = i;
}
}
public int count() {
return count;
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
public int find(int p) {
while (true) {
if (p == eleAndGroup[p]) {
return p;
}
p = eleAndGroup[p];
}
}
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
eleAndGroup[pRoot] = qRoot;
this.count--;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
1.5.5 路径压缩
- 之前我们这哎union算法中,合并树时将任意的一棵树连接到另外一棵树,这种合并方法是比较暴力的,如果我们把并查集中每一棵树的大小记录下来,然后再每次合并树的时候,把较小的树连接到较大的树上,就可以减小树的深度。只要我们保证每次合并,都能把小树合并到大树上,就能够压缩合并后新树的路径,这样就能提高find方法的效率。为了完成这个需求,我们需要另外一个数组来记录存储每个根结点对应的树中元素的个数,并且需要一些代码调整数组中的值
1.5.6 UF_Tree_Weighted API设计
1.5.7 代码实现
package com.tiger.study.DataStructure.uf;
public class UF_Tree_Weighted {
private int[] eleAndGroup;
private int count;
private int[] sz;
public UF_Tree_Weighted(int N) {
this.count = N;
this.count = N;
this.eleAndGroup = new int[N];
this.sz = new int[N];
for (int i = 0; i < sz.length; i++) {
sz[i] = 1;
}
for (int i = 0; i < eleAndGroup.length; i++) {
eleAndGroup[i] = i;
}
}
public int count() {
return count;
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
public int find(int p) {
while (true) {
if (p == eleAndGroup[p]) {
return p;
}
p = eleAndGroup[p];
}
}
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
if (sz[pRoot] < sz[qRoot]) {
eleAndGroup[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
} else {
eleAndGroup[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
this.count--;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
二. 案例分析
- 需求:某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程"的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
- 在我们的测试数据文件夹中有一个trffic_project.txt文件,它就是诚征道路统计表,下面是对数据的解释:
- 思路
- 创建一个并查集UF_Tree_Weighted(20)
- 分别调用union(0,1),union(6,9),union(3,8),union(5,11),union(2,12),union(6,10),union(4,8),表示已经修建好的道路把对应的城市连接起来;
- 如果城市全部连接起来,那么并查集中剩余的分组数目为1,所有的城市都在一个树中,所以,只需要获取当前并查集中剩余的数目,减去1,就是还需要修建的道路数目;
- 代码实现
package com.tiger.study.DataStructure.Test.UF;
import com.tiger.study.DataStructure.uf.UF_Tree_Weighted;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class Traffic_Project_Test {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("D:\\Study_Code\\src\\main\\java\\com\\tiger\\study\\DataStructure\\Test\\UF\\traffic_project.txt")));
int totalNumber = Integer.parseInt(br.readLine());
UF_Tree_Weighted uf = new UF_Tree_Weighted(totalNumber);
int roadNumber = Integer.parseInt(br.readLine());
for (int i = 0; i < roadNumber; i++) {
String line = br.readLine();
String[] str = line.split(" ");
int p = Integer.parseInt(str[0]);
int q = Integer.parseInt(str[1]);
uf.union(p, q);
}
int roads = uf.count() - 1;
System.out.println("还需要修建" + roads + "条道路才能实现畅通工程。");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41