并查集(union-¯nd set) 维护一些不相交的集合,它是一个集合的集合。每个元素
恰好属于一个集合,好比每条鱼装在一个鱼缸里。每个集合S有一个元素作为\集合代
表"rep[S],好比每个鱼缸选出一条"鱼王"。并查集提供三种操作:
MakeSet(x):建立一个新集合x。x应该不在现有的任何一个集合中出现。
Find(S, x):返回x所在集合的代表元素。
Union(x, y):把x所在的集合和y所在的集合合并。
森林表示法
可以用一棵森林表示并查集,森林里的每棵树表示一个集合,树根就
是集合的代表元素。一个集合还可以用很多种树表示,只要树中的结点不变,表示的
都是同一个集合。
合并操作
只需要将一棵树的根设为另一棵即可。这一步显然是常数的。一个优化
是:把小的树合并到大树中,这样会让深度不太大。这个优化称为启发式合并。
查找操作
只需要不断的找父亲,根就是集合代表。一个优化是把沿途上所有结点
的父亲改成根。这一步是顺便的,不增加时间复杂度,却使得今后的操作比较快。这个优化称为路径压缩,
实现代码
用p[i] 表示i 的父亲,而rank[i] 表示i 的秩,并用秩来
代替深度做刚才提到的启发式合并。
void makeset(int x)
{
p[x]=x;
rank[x]=0;
}
int findset(int x)
{
if(x!=p[x])
p[x]=findset(p[x]);
return p[x];
}
////// 使用非递归的更好理解
int findset ( int x)
{
int px = x , i;
while (px != p[px ]) px = p[px ]; // find root
while (x != px ) // path compression
{
i = p[x];
p[x ] = px;
x = i;
}
return px;
}
void union_set(int x,int y)
{
x=findset(x);
y=findset(y);
if(rank[x]>rank[y])
p[y]=x;
else
{
p[x]=y;
if(rank[x]==rank[y])rank[y]++;
}
}