并查集

Posted on 2018-08-30 18:53  亦辰落  阅读(167)  评论(0编辑  收藏  举报

例题:

并查集:

在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并

其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大

若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高

根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

洛谷P3367

传送门

先上代码:

关于并查集和路径压缩:

有a,b,c三个人

假设a和b打架了,a做了b的小弟。则令f[a]=b;

后来a打赢了c 黑社会

那么c就是a的小弟了。所以,令f[c]=a;

但是,c不知道b,这不符合要求。

所以,我们必须让c的大哥变成最大的老大。

int find(int k){
    if(f[k]==k)return k;
    return find(f[k]);
}

f[c]=find(a);

这时,我们可以使途中经过的人的大哥也变成老大

//路径压缩
int find(int k){
    if(f[k]==k)return k;
    return f[k]=find(f[k]);
}

f[c]=find(a);

而判定两个人的老大是否相等,只需用

if(find(a)==find(b))

即可

并查集支持的操作

并查集的数据结构记录了一组分离的动态集合S={S1,S2,…,Sk}。每个集合通过一个代表加以识别

代表即该元素中的某个元素,哪一个成员被选做代表是无所谓的,重要的是:如果求某一动态集合的代表两次

且在两次请求间不修改集合,则两次得到的答案应该是相同的。 动态集合中的每一元素是由一个对象来表示的,设x表示一个对象

并查集的实现需要支持如下操作:   MAKE(x):建立一个新的集合,其仅有的成员(同时就是代表)是x。

由于各集合是分离的,要求x没有在其它集合中出现过。   

UNIONN(x,y):将包含x和y的动态集合(例如Sx和Sy)合并为一个新的集合,假定在此操作前这两个集合是分离的。

结果的集合代表是Sx∪Sy的某个成员。一般来说,在不同的实现中通常都以Sx或者Sy的代表作为新集合的代表。

此后,由新的集合S代替了原来的Sx和Sy。 FIND(x):返回一个指向包含x的集合的代表

上代码:

#include<cstdio>
#include<cstring>
using namespace std;
#define init for(int i=1;i<=n;i++) fa[i]=i
const int maxn=2e4;
int fa[maxn],n,m,x,y,z;
inline int read()
{
    int S(0); char c=getchar();
    while(c>'9'||c<'0') c=getchar();
    while(c>='0'&&c<='9') S=S*10+c-'0',c=getchar();
    return S;
}
int find(int x){return fa[x]==x?fa[x]:fa[x]=find(fa[x]);}
int main()
{
    n=read(); m=read();
    init;
    for(int i=1;i<=m;i++)
    {
        z=read();
        if(z==1)
        {
            x=read(); y=read();
            int a=find(fa[x]),b=find(fa[y]);
            if(a!=b) fa[a]=b;
        }
        else
        {
            x=read(); y=read();
            if(find(fa[x])==find(fa[y])) printf("Y\n");
            else printf("N\n");
        }
    }
    return 0;
}

 

求无向图的连通分量

这里提出来强调一下

因为求无向图连通分量是个非常常用的算法。

通过并查集可以使得空间上省去对边的保存,同时时间效率又是很高的。   

需要特别指出的是,如果用链表来实现的话,最后任何在同一个集合(即连通块)中的元素,其代表指针的值都是相等的。

而采用有根树来实现的话,算法结束后,留下的依然是树的关系,因此如果希望每个元素都指向它的根的话

还需要对每个节点进行一次find操作,这样每个节点的父节点都是代表此集合的节点。在某些统计问题中,往往需要这样做

例题2:

洛谷P1195

传送门

题解:

#include<bits/stdc++.h>
#define max 1005
using namespace std;
int n,m,k,x,y,l,sum,ans;
int t[max];
struct Edge
{
    int u,v,w;
}edge[max*10];
bool operator <(Edge a,Edge b)
{
        return a.w<b.w;
}
int find(int x)
{
    return t[x]==x?t[x]:t[x]=find(t[x]);
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++) 
    t[i]=i;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
    }
    sort(edge+1,edge+m+1);
    for(int i=1;i<=m;i++)
    {
        int fx=find(edge[i].u),fy=find(edge[i].v);
        if(fx!=fy)
        {
            t[fx]=fy;
            sum++;
            ans+=edge[i].w;
        }
        if(sum==n-k)
        {
            printf("%d",ans);
            return 0;
        }
    }
    puts("No Answer");
    return 0;
}