(一)概念
并查集的基本概念见博客:https://www.cnblogs.com/xzxl/p/7226557.html
(二)用途
高效解决连接问题
- 迷宫
- 多节点的连接状态
- 集合类的实现
(三)通用模板
三要素:
- 需要记录每个结点的父结点
- 找到每个结点的祖先结点
- 将两个结点的祖先结点合并
代码:
public class Template {
int[] pre;
public void init(int len){ //初始化pre数组
pre = new int[len];
for(int i=0;i<len;i++)
pre[i] = i;
}
public int find(int x){ //查找x的祖先是谁
int p,tmp;
p=x;
while(x!=pre[x]) //一级一级去找祖先
x = pre[x];
//x变成祖先了,各下级直接指向祖先,说明下级与祖先有路径
while(p!=x){ //路径压缩
tmp=pre[p];
pre[p]=x;
p=tmp;
}
return x;
}
public void union(int x,int y){ //将两节点的祖先合并到一个祖先
int fx = find(x);
int fy = find(y);
if(fx!=fy){
pre[fx] = fy;
}
}
public boolean isSame(int x,int y){ //判断两节点的祖先是否相同
return find(x)==find(y);
}
}
(四)精选例题
(1)Leetcode 547 Friend Circles
题意:现存在N个学生,学生与学生之间存在着朋友圈,若A是B的朋友,B是C的朋友,那么我们认为A,B,C在同一个朋友圈中,现在给定NxN的矩阵A,A[i][j]=1代表i和j是朋友。问总共有几个朋友圈?
输入:A = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
输入:A = [[1,1,0],[1,1,1],[0,1,1]]
输出:1
public class Friend_Circles_547 {
public class UnionFind{
int[] pre; //记录x的上级是pre[x]
int count;
public void init(int n){
pre = new int[n];
for(int i=0;i<n;i++)
pre[i] = i;
}
public int find(int x){
while(x!=pre[x]) //一级一级去找祖先,x变成祖先了
x = pre[x];
return x;
}
public void union(int x,int y){ //将祖先合并,在这来来初始化
int fx = find(x);
int fy = find(y);
if(fx!=fy){
pre[fx] = fy;
count--; //拥有共同祖先时合并两个朋友圈,数量减一
}
}
}
public int findCircleNum(int[][] M) {
if(M==null || M.length==0)
return 0;
UnionFind uf = new UnionFind();
int len = M.length;
uf.init(len);
uf.count = len;
for(int i=0;i<len;i++){
for(int j=i+1;j<len;j++){
if(M[i][j]==1){
uf.union(i, j);
}
}
}
return uf.count;
}
}
(2)Leetcode 684 Redundant Connection
题意:给定一个数组A来表示一个图,A[i] = [u,v]表示在节点u,v之间存在边。
现在确保这个图(本身不是树)恰好通过只删除一条边可以变成树,请找出删除的边,若有多条,返回数组中最后出现的那一个。
输入:A = [[1,2],[1,3],[2,3]]
输出:[2,3]
输入:A = [[1,2], [2,3], [3,4],[1,4], 1,5]]
输出:[1,4]
public class Redundant_Connection_684 {
public class UnionFind{
int[] pre;
public void init(int len){
pre = new int[len+5];
for(int i=0;i<len+5;i++)
pre[i] = i;
}
public int find(int x){
while(x!=pre[x]) //一级一级去找祖先,x变成祖先了
x = pre[x];
return x;
}
public boolean union(int x,int y){ //将祖先合并,在这来来初始化
int fx = find(x);
int fy = find(y);
if(fx==fy){
return false; //已经有共同祖先了,不需要合并就代表有圈
}else{
pre[fx] = fy;
return true;
}
}
}
public int[] findRedundantConnection(int[][] edges) {
UnionFind uf = new UnionFind();
int len = edges.length;
uf.init(len);
int[] res = new int[2];
for(int i=0;i<len;i++){
boolean flag = uf.union(edges[i][0], edges[i][1]);
if(flag==false){
res[0]=edges[i][0];
res[1]=edges[i][1];
break;
}
}
return res;
}
}
(3)Leetcode 959 Regions Cut By Slashes
题意:给定一个NxN的矩阵,矩阵是由1x1的小格子组成,每个小格子中可能包含’/’, ’\’, ’’三种情况,返回矩阵中的连通区域。
输入:A = [“ /”, “/ ”]
输出:2
输入:A = [“\/”, “/\”]
输出:4
public class Regions_Cut_By_Slashes_959 {
public class UnionFind{
int[] pre;
public void init(int len){ //每一个正方形两对角线变成4个三角形,进行编号
pre = new int[len*len*4];
for(int i=0;i<len*len*4;i++)
pre[i] = i;
}
public int find(int x){
while(x!=pre[x]) //一级一级去找祖先,x变成祖先了
x = pre[x];
return x;
}
public void union(int x,int y){ //将祖先合并,在这来来初始化
int fx = find(x);
int fy = find(y);
if(fx!=fy){
pre[fx] = fy;
}
}
}
public int regionsBySlashes(String[] grid) {
int n = grid.length; //N*N的网格
UnionFind uf = new UnionFind();
uf.init(n);
int res = 0;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
char cur = grid[i].charAt(j);
int root = (i*n+j)*4;
if (cur != '\\'){
uf.union(root + 0, root + 1);
uf.union(root + 2, root + 3);
}
if (cur != '/') {
uf.union(root + 0, root + 2);
uf.union(root + 1, root + 3);
}
// north south
if (i + 1 < n)
uf.union(root + 3, (root + 4 * n) + 0);
if (i - 1 >= 0)
uf.union(root + 0, (root - 4 * n) + 3);
// east west
if (j + 1 < n)
uf.union(root + 2, (root + 4) + 1);
if (j - 1 >= 0)
uf.union(root + 1, (root - 4) + 2);
}
}
for (int x = 0; x < 4 * n * n; x++) {
if (uf.find(x) == x)
res++;
}
return res;
}
}
(4)Leetcode 765 Couples Holding Hands (待更)
有N对夫妇随机坐在2*N个座位上,现在要使每对夫妇都能牵手(相邻即可),我们需要交换一些人的座位,问最少需要交换多少次才能满足题意。
夫妇使用数字表示,(0,1)是一对夫妇,(2,3)是一对夫妇,以此类推。
提示:最多存在30对夫妇。