并查集
并查集运用树的结构,通过判断每个点的最终双亲是否相同来判断是不是在一个集合中
一般步骤:
1.查找:查找一个点的最终双亲---递归
2.合并:如果两个相通点的双亲相同,说明已经在一个集合中,不需要合并,不同则将两个集合合并
例题:村村通
给定n个村庄和m条已经通的路,问还需要在休几条路
思路:将通的路算作一个集合,求出有几个独立的集合
将村庄比较树的结点,与它相通则表示为它的孩子;
int par[N] 记录每个村庄的最终双亲
首先将每个村庄的双亲记为自己:
void init(int n) {
for (int i = 0; i <= n; i++) {
par[i] = i;
Rank[i] = 1;
}
}
然后开始输入相通的村庄,并进行合并---其中Rank[] 的作用记录每科树的深度,使得在合并时让大树作为小树的双亲(减小树的深度)
int find(int x) {//找最终双亲
if(par[x] == x) return x;
else return par[x] = find(par[x]);
}
void unite(int x, int y) {
x = find(x);
y = find(y);
if(x == y) return ;
if(Rank[x] <= Rank[y]) par[x] = y;
else par[y] = x;
if(Rank[x] == Rank[y]) Rank[x]++;
}
此时所有集合都已经找到了,我们只需要遍历每一个村庄,如果每个村庄的双亲就是自己说明它是集合的根节点 ans++;
最后ans就是集合的个数,要想让所有集合相通 需要添加 ans-1 条路。
全部代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int par[N], Rank[N]; //par 祖先,Rank秩优化
void init(int n) {
for (int i = 0; i <= n; i++) {
par[i] = i;
Rank[i] = 1;
}
}
int find(int x) {
if(par[x] == x) return x;
else return par[x] = find(par[x]);
}
void unite(int x, int y) {
x = find(x);
y = find(y);
if(x == y) return ;
if(Rank[x] <= Rank[y]) par[x] = y;
else par[y] = x;
if(Rank[x] == Rank[y]) Rank[x]++;
}
bool same(int x, int y) {
return find(x) == find(y);
}
//init(int) 初始化,unite(x,y) 合并 same 是否相同
// find() 找祖先
int main()
{
int n,m;
while(cin>>n)
{
if(n==0) break;
cin>>m;
init(n);
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
unite(x,y);
}
int ans = 0;
for(int i=1;i<=n;i++)
{
if(i==find(i)) ans++;
}
cout<<ans-1<<endl;
}
return 0;
}