并查集,路径压缩,按元素数量合并,带权并查集

来源:互联网 发布:webapi实例连接数据库 编辑:程序博客网 时间:2024/06/06 02:53

并查集 由三个部分组成:1个fa[x]数组(初始化),1个查询函数(找根),1个并集 函数

  1. 定义 两个元素属于一个集合的充分必要条件是两个元素的根相等,

  2. 并集就是把 A的根改成B的指向B的根 ,即fa[root(A)]=root(B);

1.最基本的并查集写法


int fa[N];for(int i=0;i<N;i++) fa[i]=i;//初始化int f_r(int x){     //找根 ;    while(x!=fa[x])x=fa[x];    return x;}bool join(int r1,int r2){        //并集    if(r1==r2) return false;    fa[r2]=r1;    //fa[r1]=r2;    return true;}

2.按数量合并和路径压缩

首先我们来看按数量合并

在并集的时候把集合数量小的 集合插入到数量多的集合中  一般情况下能减少 (平衡)整个树的高度;

然后我们知道fa[i]数组中根节点的值fa[x]=x是没有意义的;所以这个时候我们可以把它拿来记录这个集合的元素数量;

这时候你会说那怎么判断fa[x]是x的父亲还是x这个集合的数量?

所以我们用数量的负值来赋值给fa[root];即初始化的情况下,每个集合就是元素本身,那fa[i]=-1;

这里用memset(fa,-1,sizeof(i))就可以做到

那查询函数的条件也要改变,元素是根的条件是fa[i]<0;

合并函数 要把集合元素少(fa[r1]大)的集合合并到元素多(fa[r2]小)的集合中;所以我们要更新大集合的个数,在合并小集合

int fa[N];for(int i=0;i<N;i++) fa[i]=-1;//初始化int f_r(int x){     //找根 ;    while(fa[x]>0){        x=f[x];    }    return x;}bool join(int r1,int r2){        //并集    if(r1==r2) return false;    if(fa[r1]<fa[r2]){        fa[r1]+=fa[r2]; //更新 后来发现这里写错了r1,r2是根,而fa[root]才是集合的个数的负值;        fa[r2]=r1;  //合并     }    else{        fa[r2]+=fa[r1];         fa[r1]=r2;    }    return true;}

3.路径压缩

因为我们并查集查询的复杂度取决于树的高度,所以如果一个并查集在某些情况下退化策划成链表会大大影响查询效率;

所以我们在找根的时候“顺便”把所有非根结点连接到根上,那这颗树就是高度为2的树了;

那求根的代码有两种写法 下面给出代码 用并查集的性质来理解代码;

递归求法(可能会RE)

int find_root(int x){ return fa[x]>0?fa[x]=find_root(fa[x]):x;}

非递归求法


int find_root(int x){    int root=x,t;    while(fa[root]>0) root=fa[root];    while(x!=root){        t=fa[x]; //保留父亲节点        fa[x]=root;         x=t;    }    return x;}

4.带权并查集

并查集只是表示了两个元素之间的从属关系,那如果我们引入其他数组就可以达到在 合并集合 的时候更新

例题 POJ 1988;

这个dis[i]表示i的底端有几个元素;那在路径压缩的时候要从底到顶

更新 dis[x]+=dis[tmp];

#include <iostream>    #include <cstdio>    using namespace std;    const int maxn=31000;    int father[maxn],dis[maxn];    int find_root(int x){        if(father[x]>0){            int tmp=father[x];            father[x]=find_root(father[x]);            dis[x]+=dis[tmp];            return father[x];        }        return x;    }    void combine(int x,int y){        int tmp=-father[y];        father[y]+=father[x];        father[x]=y;        dis[x]+=tmp;    }    int main(){        //freopen("in.txt","r",stdin);        int m;        scanf("%d",&m);        for(int i=0;i<maxn;i++){            father[i]=-1;            dis[i]=0;        }        while(m-- >0){            char ch;            cin>>ch;            if(ch=='M'){                int a,b;                scanf("%d%d",&a,&b);                if(find_root(a)!=find_root(b)) combine(find_root(a),find_root(b));            }            else{                int x;                scanf("%d",&x);                find_root(x);                printf("%d\n",dis[x]);            }        }        return 0;    }