并查集
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
主要操作
- 初始化:把每个点所在集合初始化为其自身。通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
- 查找:查找元素所在的集合,即根节点。
- 合并:将两个元素所在的集合合并为一个集合。通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
例题
首先在地图上给你若干个城镇,这些城镇都可以看作点,然后告诉你哪些对城镇之间是有道路直接相连的。最后要解决的是整幅图的连通性问题。比如随意给你两个点,让你判断它们是否连通,或者问你整幅图一共有几个连通分支,也就是被分成了几个互相独立的块。像畅通工程这题,问还需要修几条路,实质就是求有几个连通分支。n个连通分支,就是n-1条路。
//畅通工程
#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
int fa[maxn];
int sum;
int find(int x){//找祖先
if(x == fa[x]){
return x;
}
else{
fa[x] = find(fa[x]);
return fa[x];
}
}
void unit(int x, int y){//不同祖先就把他们连通
int fx = find(x);
int fy = find(y);
if (fx == fy){
return;
}
fa[fx] = fy;
}
bool same(int x, int y){//判断是否同祖先
if (find(x) == find(y)){
return true;
}
return false;
}
int main()
{
std::ios::sync_with_stdio(false);
int n,m,i,x,y,root1,root2;
while(cin>>n>>m&&n){
for(int i=1;i<=n;i++) fa[i]=i;
int sum=n-1;//初始有n-1条路
while(m--) {
cin>>x>>y;
if(!same(x,y)){//若不同祖先 ,即不连通
unit(x,y);//就把他们连通
sum--;//需要的路就少一条
}
/*root1=find(x);
root2=find(y);
if(root1!=root2)//若不同祖先 ,即不连通
{
fa[root1]=root2;//就把他们连通
sum--; //需要的路就少一条
}*/
}
cout<<sum<<endl;
}
}