并查集
理论基础
等价关系:自反性、对称性和传递性
等价关系能够将对象分为多个等价类。
应用:动态连通性问题
术语:
- 对象:触点
- 整数对:连接
- 等价类:连通分量或分量
成本模型:访问任意数组元素的次数,无论读写
union-find
以触点为索引的 id[] 数组来确定两个触点是否存在于相同的连通分量中
public class UF{
int[] id; // 分量id(以触点作为索引)
int count; // 分量数量,初始化为 N
// 初始化分量 id 数组
public UF(int N){
count=N;
id = new int[N];
// 每个分量只含触点本身
for(int i=0;i<N;i++){
id[i] = i;
}
}
// 如果p和q存在于同一个分量中则返回true
boolean connected(int p, int q){
return find(p) == find(q);
}
// p所在的分量的标识符
int find(int p){}
// 如果两个触点在不同的分量中,则会将两个分量归并且分量总数减一
void union(int p, int q){}
}
quick-find 算法
保证当且仅当 id[p] == id[q] 时 p 和 q 是连通的,即在同一个连通分量中的所有触点在 id[] 中的值必须全部相同。
-
检查 p 和 q 是否已在同一个连通分量中
-
已在,则不需要采取任何行动
-
不在,p 所在的连通分量中的所有触点的 id[] 均为同一个值,q 同理为另一个值
遍历整个数组,将所有和 id[p] 相等的元素值变为 id[q]
-
O(N2)
// p所在的分量的标识符; O(1)
int find(int p){
return id[p];
}
// 将p和q归并到相同的分量中; O(N)
void union(int p, int q){
int pID = find(p);
int qID = find(q);
// 如果p和q已经在相同的分量之中则不需要采取任何行动
if(pID==qID){
return;
}
// 将p的分量重命名为q的名称
for(int i=0;i<id.length;i++){
if(id[i]=pID){
id[i] = qID;
}
}
count---; // 分量总数减一
}
quick-union算法
链接:每个触点所对应的 id[] 元素都是同一个分量中的另一个触点的名称(可能是它自己)。
- 初始化时,每个节点的链接都指向自己
- id[] 数组用父链接的形式表示了一片森林
- 根节点的链接指向自己,表示一个分量。
当且仅当分别由两个触点开始的这个过程到达了同一个根触点时它们存在于同一个连通分量中。
当且仅当两个触点存在于相同的分量之中时它们对应的节点才会在同一棵树中。
O(N2)
// 找出分量的名称; O(h)
int find(int p){
// 直到达到根触点
while(p!=id[p]){
// 从给定触点开始,由它的链接得到另一个触点
p = id[p];
}
return p;
}
// 将 p 和 q 的根节点统一; O(h)
void union(int p, int q){
int pRoot = find(p); // 找到其根触点
int qRoot = find(q);
if(pRoot == qRoot){
return;
}
id[pRoot] = qRoot; // 将一个根触点链接到另一个
count--;
}
加权quick-union算法
与其在 union() 中随意将一棵树连接到另一棵树,选择记录每一棵树的大小并总是将较小的树连接到较大的树上。
O(logN)
public class WeightedQuickUnionUF{
int[] id; // 父链接数组(由触点索引)
int[] sz; // (由触点索引的)各个根节点所对应的分量的大小
int count; // 连通分量的数量
public WeightedQuickUnionUF(int N){
count=N;
for(int i=0;i<N;i++){
id[i] = i;
}
for(int i=0;i<N;i++){
sz[i] = ;
}
}
// 跟随链接找到根节点; O(lgN)
int find(int p){
// 直到达到根触点
while(p!=id[p]){
// 从给定触点开始,由它的链接得到另一个触点
p = id[p];
}
return p;
}
// O(lgN)
void union(int p, int q){
int i = find(p);
int j = find(q);
if(i==j){
return;
}
// 将小树的根节点连接到大树的根节点
if(sz[i]<sz[j]){
id[i] = j;
sz[j] += sz[i];
}else{
id[j] = i;
sz[i] += sz[j];
}
count--;
}
}