并查集
1. 概论
定义:
并查集是一种树形的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。
主要构成:
并查集主要由一个整型数组
pre[]
和两个函数find()
、join()
构成
pre[]
记录每个点的前驱节点。
find(x)
用于查找指定节点x
属于哪个集合,返回该集合的根节点(元节点)
join(x, y)
用于合并两个节点x
和y
适用:
- 求连通分支数(如果一个图中所有点都存在可达关系(直接或间接相连),则此图的连通分支数为1;如果此图有两大子图各自全部可达,则此图的连通分支数为2……)
- 求图中任意两个节点是否连通(存在路径)。
2. 并查集的现实意义:
对于若干个具有层级关系的连通图(树),快速查找两个节点是否连通,不关心他们具体是如果连通的,
内部结构是怎样的,甚至根节点是哪个,都不重要。所以并查集在初始化时,教主可以随意选择(就不必再搞什么武林大会了),只要能分清敌友关系就行。
3. find()
函数实现
定义
find()
函数要实现找到节点x
所属集合的根节点,可以结合pre[]
数组找,通过pre[]
数组一直找x
的上级,直到pre[x] == x
public int find(int x){
while(pre[x] != x){
x = pre[x];
}
return x;
}
**优化:**路径压缩优化
上述再找根节点的时候,需要一级一级向上找,不过这个步骤是不必要的,我们只关心
x
的根节点是谁,不关心x
经过了多少节点才找到根节点,因此把x
的上级直接设成根节点最好,可以提升效率,这被称为路径压缩。
public int find(int x){
if(pre[x] == x) return x;
reutrn pre[x] = find(pre[x]); // 找到根节点,然后将x的前驱节点设成根节点。
}
4. join()
函数实现
定义:
join(x, y)
函数是将x
,y
两个节点建立联系,也就是将x
与y
所属的连通图连起来形成一个连通图。如果x
,y
属于一个图,则不用操作,如果不属于,则找到两者的根节点,将一个根节点的父节点设置成另一个根节点即可。
public void join(int x, int y){
int px = find(x), py = find(y);
if(px == py)return;
pre[px] = py;//让py 作为 px 的前驱。
}
优化: 加权标记优化
上述操作是随机选择,我们直到两个树的高度可能不同,如果将高度高的树接在高度低的树上则会增加总体高度,影响效率。因此给每个节点引入一个权值
rank[i]
,该权值记录节点的高度,在合并的时候通过比较节点高度,将高度低的接在高度高的树上。
public boolean join(int x, int y){
int px = find(x), py = find(y);
if(px == py)return false;
if(rank[px] > rank[py])
pre[py] = px;
else{
// 如果两棵树高度相同,将 py 当最终根节点时py的高度会 + 1
if(rank[x] == rank[y]){
rank[y] ++;
}
pre[px] = py;
}
return true;
}
5. 查看两个节点是否连通
public boolean connect(int x, int y) {
return find(x) == find(y);
}
6. 查看连通集合的个数
使用路径压缩的并查集,如果
pre[x] = x
就说明存在一个集合。
public int getConnectNum(){
int res = 0;
for(int i = 0; i < pre.length; i ++){
if(pre[i] = i)res ++;
}
return res;
}
7. 总体代码实现
public calss UnionSet{
int[] pre; // 前驱数组
int[] rank; // 权值数组
public Unionset(int n){
pre = new int[n];
rank = new int[n];
this.init(n);
}
// 初始化
private void init(int n){
for(int i = 0; i < n; i ++){
pre[i] = i;
ran[i] = 1;
}
}
public int find(int x){
if(pre[x] == x) return x; //递归出口:x的上级为 x本身,即 x为根结点
return pre[x] = find(pre[x]); //此代码相当于先找到根结点 rootx,然后 pre[x]=rootx
}
public boolean join(int x, int y){
int px = find(x), py = find(y);
if(px == py)return false;
if(rank[px] > rank[py])
pre[py] = px;
else{
// 如果两棵树高度相同,将 py 当最终根节点时py的高度会 + 1
if(rank[x] == rank[y]){
rank[y] ++;
}
pre[px] = py;
}
return true;
}
public boolean connect(int x, int y) {
return find(x) == find(y);
}
public int getConnectNum(){
int res = 0;
for(int i = 0; i < pre.length; i ++){
if(pre[i] = i)res ++;
}
return res;
}
}