彻底理解并查集
并查集也叫不相交集合(disjoint-set),是一种比较高级的数据结构。
为什么叫不相交集合呢?
可以看到下面的图中有五种颜色,代表他们有五个集合,意味着他们都是互相独立不相交的,所以称为不相交集合。
那我们应该如何保存这种数据结构呢,最简单的办法就是用数组来保存,可以看到上面第一行数字的0,1,2,3对应的就是数组的下标。
那为什么又叫并查集呢?
这是因为这种数据结构最重要的两种操作就是合并和查找。
那什么叫合并呢?
我们把上面的图中第一组和第二组合为一组如下图,第三四五合为一组。那现在就只剩下两组集合了。
什么叫查找呢?
给一个结点6,要求你判断他属于哪个集合?显然,从图中很自然的看到结点6在粉红色的集合中,属于第二组。
实现方法:
先定义一个数组
int Tree[N];
怎么用数组实现查找呢?
我们将数组的值为-1的结点选为这一组的代表(根),比如上图0点结点代表第一组,4结点代表第二组。
组内的其他结点的值全为代表的数组下标值(这里使用了路径压缩技术),如1,2结点的值全为0,指他们的根为0结点。
这样随便指定一结点,很快就能找到他的根在哪。
int FindRoot(int x)
{
if(Tree[x]==-1)
return x;
else
{
int tmp=FindRoot(Tree[x]);//把查找路上的结点全指向根
Tree[x]=tmp;
return tmp;
}
}
怎么用数组实现合并呢?
从图中可以很容易看出来,把第二组的代表的值改成第一组代表的数组下标即可。
给定两个结点,如果不是一组,合并他们
x=FindRoot(0);
y=FindRoot(4);
if(x!=y)
{
Tree[x]=y;
}
用途之一、一般用来确定无向图中连通子图(集合)的个数
用途之二、一般用来解决最小生成树问题比较多
用途一
求出连通子图的个数n,这样还可以求出最少还需几条边(n-1)可以连接所有的连通子图。
题目描述:
给定一个无向图和其中的所有边,判断这个图是否所有顶点都是连通的。
输入:
每组数据的第一行是两个整数 n 和 m(0<=n<=1000)。n 表示图的顶点数目,m 表示图中边的数目。如果 n 为 0 表示输入结束。随后有 m 行数据,每行有两个值 x 和 y(0
输出:
对于每组输入数据,如果所有顶点都是连通的,输出"YES",否则输出"NO"。
样例输入:
4 3
1 2
2 3
3 2
3 2
1 2
2 3
0 0
样例输出:
NO
YES
来源:
2011年吉林大学计算机研究生机试真题
我们先求出有几个连通子图,如果只有一个连通子图就输出YES,否则NO.
int main()
{
int n,m,x,y;
while(scanf("%d",&n)!=EOF&&n!=0)
{
scanf("%d",&m);
for(int i=1;i<=n;i++)
Tree[i]=-1;
int ans=0;
for(int i=1;i<=m;i++)
{
scanf("%d %d",&x,&y);
x=FindRoot(x);
y=FindRoot(y);
if(x!=y)
{
Tree[x]=y;
ans++;
}
}
if(ans==n-1)
printf("YES\n");
else
printf("NO\n");
}
}
用途二、
求最小生成树,这里主要是克鲁斯卡尔(Kruskal)算法,算法主要的核心就是贪心算法+并查集。贪心算法在每次都选择局部最优解(边权值最小的边),保证最终得到全局最优解。
我们按照按如下步骤求解最小生成树:
1.初始时所有结点属于孤立的集合。
2.按照边权递增顺序遍历所有的边,若遍历到的边两个顶点仍分属不同的集合(该边即为连通这两个集合的边中权值最小的那条)则确定该边为最小生成树上的一条边,并将这两个顶点分属的集合合并。
3.遍历完所有边后,原图上所有结点属于同一个集合则被选取的边和原图中所有结点构成最小生成树;否则原图不连通,最小生成树不存在。
更多资料可以参考
1、算法导论第二版
2、大话数据结构
3、王道论坛计算机考研机试指南
4、http://www.cnblogs.com/cyjb/p/UnionFindSets.html(基础概念)
5、杭电ACM的ppt.