并查集算法:
首先我们要明白什么是并查集算法,百度上面的定义是:
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
我们可以看出并查集算法适用于集合合并问题中,其优点是时间复杂度低,速度快。
我为什么会接触到并查集算法。是因为我之前在学生成最小树中Kruskal算法这里碰到过。
我们这里先来看并查集算法。其实我个人感觉并查集算法也可以叫做ancestors算法,因为每个节点都是有一个父母的,而且最后判断两个集合是否是属于同一个集合就是判断他们的祖先是否相同。
我们现在正式开始讲并查集算法算法。这里依托于一道题目来讲。
问题描述:
给定n个人,分别编号为1~n,初始时每个人自成一个组,接下来进行m次合并操作,每次合并操作把x和y所在的两个小组合并成一个小组(如果x和y已经属于同一个小组,则不做改变)。接下来是k个查询,请你回答a和b是否属于同一个小组。
输入:
输入第一行是一个正整数n(0<n<100)。第二行是一个正整数m,接下来m行每行两个正整数x和y(1<x,y<=n)。再接下来是一个正整数k,最后的k行每行两个正整数a和b。
输出:
总共k行,每行输出yes或者no。
这一道题目是典型的并查集算法题。因为就是将一些元素进行合并,最后判断两个元素是否是属于同一个集合。
首先我们需要一个父母数组用来存储这些元素的父母信息(因为,我们必须通过父母的父母的父母。。来将各个元素联系起来)如图一所示。
我们先进行一些初始化操作。将parent数组初始化,应为一开始,他们没有父母,所以元素是指向自己的。
for(int i=1;i<=n;i++){
parent[i]=i;
}
然后我们根据元素与元素之间的关系,来将元素建立其逻辑关系。
for(int i=0;i<m;i++){
scanf("%d %d",&x,&y);
x=find(parent,x);
y=find(parent,y);
if(x!=y){//一定,要x!=y,否则一个集合里面就会有两个祖先,这明显是不可能的。
parent[y]=x;//注意这里parent[x]=y,也是没关系的,因为这里谁是父母是我们自己定的。
}
}
那现在我们怎么去查找祖先呢?
int find(int parent[],int x)
{
while(parent[x]!=x){
x=parent[x];
}
return x;
}
总之就是,将各个元素通过parent与ancestor链接起来。
完整代码:
#include<stdlib.h>
#include<stdio.h>
int find(int parent[],int x)
{
while(parent[x]!=x){
x=parent[x];
}
return x;
}
int main()
{
int n,m,k;
int x,y;
int parent[100];
scanf("%d",&n);
for(int i=1;i<=n;i++){
parent[i]=i;
}
scanf("%d",&m);
for(int i=0;i<m;i++){
scanf("%d %d",&x,&y);
x=find(parent,x);
y=find(parent,y);
if(x!=y){
parent[y]=x;
}
}
scanf("%d",&k);
for(int i=0;i<k;i++){
scanf("%d %d",&x,&y);
if(find(parent,x)==find(parent,y)){
printf("yes\n");
}
else{
printf("no\n");
}
}
return 0;
}
图一:
并查集优化方式:路径压缩,即如下图所示