并查集

并查集

并查集运用树的结构,通过判断每个点的最终双亲是否相同来判断是不是在一个集合中

一般步骤:

1.查找:查找一个点的最终双亲---递归

2.合并:如果两个相通点的双亲相同,说明已经在一个集合中,不需要合并,不同则将两个集合合并

 

例题:村村通

给定n个村庄和m条已经通的路,问还需要在休几条路

思路:将通的路算作一个集合,求出有几个独立的集合

将村庄比较树的结点,与它相通则表示为它的孩子;

int par[N] 记录每个村庄的最终双亲

首先将每个村庄的双亲记为自己:

void init(int n) {
    for (int i = 0; i <= n; i++) {
        par[i] = i;
        Rank[i] = 1;
    }
}
View Code

然后开始输入相通的村庄,并进行合并---其中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]++;
}
View Code

此时所有集合都已经找到了,我们只需要遍历每一个村庄,如果每个村庄的双亲就是自己说明它是集合的根节点 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;
    }

 

posted @ 2020-02-29 23:27  精神小伙儿  阅读(140)  评论(0编辑  收藏  举报