做了几道并查集的题,在这里做一个小小的总结,以表达我对这个精简的数据结构的浅显的理解。。。
先说说用途:
1、并查集,顾名思义分为并和查。
并:即:将两个完全不相交的集合合并为一个集合。
查:查询某个元素属于哪一个集合,一般用来判断两个元素是否属于同一集合。
2、目前我所接触到的它的实际用途:
(1)、求解最小生成树(克鲁斯卡尔算法)
(2)、判断图是否连通或是否有环
(3)、确定无向图的连通子图个数
(4)、确定某一集合或者团体有多少元素(或个体)。
接下来就说说并查集的三种操作(三个函数):
1、Make_Set(x):把每一个元素初始化为一个集合。
初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。
2、GetParent(x) :查找一个元素所在的集合。
其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
3、Merge(x,y):合并x,y所在的两个集合
合并两个不相交集合操作很简单:
利用GetParent函数找到这两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。
对于并查集的优化,一个是路径压缩,一个是按秩合并,这也是这个数据结构的精髓所在和最为优美的地方。
直接看代码:
int parent[MAX]; /* father[x]表示x的父节点*/
int rank[MAX]; /* rank[x]表示x的秩*/
// 初始化集合
void Make_Set(int x)
{
parent[x] = x; //根据实际情况指定的父节点可变化
rank[x] = 0; //根据实际情况初始化秩也有所变化
}
// 查找x元素所在的集合,回溯时压缩路径
int GetParent(int a){
if(parent[a]!=a)
parent[a]=GetParent(parent[a]); //这个回溯时的压缩路径是精华
return parent[a];
}
/*
按秩合并x,y所在的集合
下面的那个if else结构不是绝对的,具体根据情况变化
但是,宗旨是不变的即,按秩合并,实时更新秩。
*/
void Merge(int x, int y)
{
x = GetParent(x);
y = GetParent(y);
if (x == y) return;
if (rank[x] > rank[y])
{
parent[y] = x;
}
else
{
if (rank[x] == rank[y])
{
rank[y]++;
}
parent[x] = y;
}
}