以一道力扣题来引出这个问题,此题就是查并集的原型问题,如果当问题关注连通团体的数量时,可以使用该算法来解决问题
算法包含两个关键的函数和一个关键的parent数组:
上级:每个团队里的代表任务,每个团队里有且只有一个,用它来标识整个团队,每个团队的其他元素通过find算法最终都能找到唯一的上级
parent数组:对于parent[i]=j,它表示的是对于顶点i,它的上级是j,一般情况下会将它初始化为parent[i]=i,因为每个顶点都至少是自己的上级
for (int i = 1; i <= n; i++) {
parent[i]=i;
}
find函数:如果当前的顶点上级不是自己的时候(已经和别的点进行连通了),去寻找到最顶层的上级
private int find(int point) {
return parent[point]==point?point:find(parent[point]);
}
uinon函数:如果当前两个点还没有联系,但是函数判断两点应该有联系,使用此函数将一个点的上级添加为另一个点的上级
public void union(int index1, int index2) {
parent[find(index1)] = find(index2);
}
整体的解法如下:
int[] parent;
public int findCircleNum(int[][] isConnected) {
int cities = isConnected.length;
parent = new int[cities];
for (int i = 0; i < cities; i++) {
parent[i] = i;
}
//对于如题的数组可以只看数组的上三角
for (int i = 0; i < cities; i++) {
for (int j = i + 1; j < cities; j++) {
if (isConnected[i][j] == 1) {
union(i, j);
}
}
}
//parent[i]==i相当于是每个团体的标志性人物,也就是团体的总个数
int provinces = 0;
for (int i = 0; i < cities; i++) {
if (parent[i] == i) {
provinces++;
}
}
return provinces;
}
//随意选择一个人当另一个人的上级
public void union(int index1, int index2) {
parent[find(index1)] = find(index2);
}
public int find(int index) {
return parent[index]==index?index:find(parent[index]);
}
为什么可以使用parent[i]==i来判断团体的数量?
对于如下的算例:
算法一开始初始化为三个小团体,parent数组最后打印出来是[1,1,2],表示0->1,1->1,2->2,由于初始化时我们认定parent[i]=i,即i的上级是自己,即一个团体中最上级的上级肯定是自己,因此可以通过parent[i]==i来判断团体的数量
相似的字符串
此问题对比原型问题其实只改变了一个条件,如果两个顶点要被分为一组,必须是异位词
int[] parent;
public int numSimilarGroups(String[] strs) {
int m=strs.length;
parent=new int[m];
int n=strs[0].length();
for (int i = 0; i < m; i++) {
parent[i]=i;
}
for (int i = 0; i < m; i++) {
for (int j = i+1; j < m; j++) {
int fi=find(i);
int fj=find(j);
if(fi!=fj&&isValid(strs[i],strs[j],n)){
//如果两个字符串满足异位词就设置为一组
union(i,j);
}
}
}
int res=0;
for (int i = 0; i < m; i++) {
if(parent[i]==i)res++;
}
return res;
}
private boolean isValid(String str, String str1, int n) {
int num=0;
for (int i = 0; i < n; i++) {
if(str.charAt(i)!=str1.charAt(i))num++;
if(num>2)return false;
}
return true;
}
//随意选择一个人当另一个人的上级
public void union(int index1, int index2) {
parent[find(index1)] = find(index2);
}
public int find(int index) {
return parent[index]==index?index:find(parent[index]);
}
多余的边
该题理解起来比较晦涩,其大致意思是所给的三条边中,有一条是可以去掉的,且去掉之后依旧可以让剩余部分形成一个整体,并且如果可以删除多条边,那么选择最后一条边进行删除
- 如果两个顶点属于不同的连通分量,说明在遍历到当前的边之前,两个顶点并不连通,因此当前边不会导致环的出现,于是我们合并这两个顶点的连通分量
- 如果两个顶点属于同一个连通分量了,说明在遍历到当前边之前,两个顶点已经连通了,因此当前的边导致了环的出现,直接返回当前边就行了
int[] parent;
public int[] findRedundantConnection(int[][] edges) {
int n = edges.length;
parent = new int[n + 1];
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
for (int i = 0; i < n; i++) {
int edge1 = edges[i][0], edge2 = edges[i][1];
int fi = find(edge1), fo = find(edge2);
if (fi != fo) {
union(i, j);
} else {
return edges[i];
}
}
return new int[]{};
}
public void union(int index1, int index2) {
parent[find(index1)] = find(index2);
}
public int find(int index) {
return parent[index] == index ? index : find(parent[index]);
}
从此题我们可以总结出并查集算法的另一个作用:找到导致成环的最后一条边