初见安~这里是一个差点被遗忘了的并查集专题:)
并查集
顾名思义——并查集,就是合并,搜查集合。其本质意义为:
有n个集合的数,我们为了区别这几个集合,每个集合选择一个数作为代表,看某两个数是否在同一集合中,则只需看它们所在集合的代表数是否相同。
如果还没理解的话:
则我们可以设1为集合1的代表,4为集合2的代表。
在数组fa(father)中就可以存x点的所在集合代表:
看两个点是否在同一集合中,只要看两点的fa是否一样即可。
然而——
多数情况下我们是不可能一开始就确定每个点直属的集合的,往往是一种间接的关系。比如在上图中,实际情况可以是:
但我们仍然知道,1、2、3三个点是在同一集合里的。所以这时候我们就需要一个并查集专用函数之一:get操作来找点x真正的所在集合的代表点。
由上方情况我们可以得知:一个点如果它本身就是该集合的代表点,会有fa[ x ] = x。同理,就有了以下操作:
int get(int x)
{
if(fa[x]==x) return x;//已经到了这个集合的代表节点,返回即可。
return get(fa[x]);//否则继续递归找其父节点。
}
大致就是这个样子了:
当然,每个点在连边之前各自的根节点就是它自己,初始化为fa[ x ] = x;
所以有时我们会发现:递归的层数有可能会很大,甚至有时候如果在无向图中知道a、b相连,fa存fa[ a ] = b或者fa[ b ] = a都有可能判定的时候会出现有两个点的根连不到一块儿去的情况。即设a本就在集合1中,fa[ a ] = b后本应将b拉入集合1,却变成了a、b点在外单独成立一个集合的状态。所以我们在存fa的时候,存的是点的根节点相连。即
这就是并查集的正常操作了:)用到并查集的算法:最小生成树·Kruskal
下面我们来看一个例题:【这里是传送门:洛谷P1536
题目描述
某市调查城镇交通状况,得到现有城镇道路统计表。表中列出了每条道路直接连通的城镇。市政府“村村通工程”的目标是使全市任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要相互之间可达即可)。请你计算出最少还需要建设多少条道路?
输入格式:
每个输入文件包含若干组测试测试数据,每组测试数据的第一行给出两个用空格隔开的正整数,分别是城镇数目N(N<1000)和道路数目M;随后的M行对应M条道路,每行给出一对用空格隔开的正整数,分别是该条道路直接相连的两个城镇的编号。简单起见,城镇从1到N编号。
注意:两个城市间可以有多条道路相通。例如:
3 3 1 2 1 2 2 1 这组数据也是合法的。当N为0时,输入结束。
输出格式:
对于每组数据,对应一行一个整数。表示最少还需要建设的道路数目。
输入样例#1:
4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0
输出样例#1:
1
0
2
998
题解:
这是一个很基础的纯并查集操作题:已有的边全部连上,可以将图划分为n个互不连通集合,看有多少个集合就需要再连多少个-1条边来把它们连起来。
下面是代码及详解——
#include<bits/stdc++.h>
using namespace std;
int f[2000];//fa数组
int get(int x)
{
if(f[x]==x) return x;
return get(f[x]);
}
int main()
{
register int m,n,a,b,ans=0;
while(scanf("%d",&n))
{
if(n==0) return 0;//读入完毕
cin>>m;
ans=0;
for(register int i=1;i<=n;i++)
f[i]=i;//初始化
for(register int i=1;i<=m;i++)
{
cin>>a>>b;
f[get(a)]=get(b);//存图连边
}
for(register int i=1;i<=n;i++)
{
if(f[i]==i) ans++;//有多少个根节点就是有多少个集合
}
cout<<ans-1<<endl;//n个集合需要连n-1条边,树形
}
return 0;
}
迎评:)
——End——