并查集模板

35 篇文章 0 订阅

#并查集

并查集(合并与查询)
假设有五个人,A,B,C,D,E。A和B是朋友,B和C是朋友,D和E是朋友,那么我们可以看到A-B-C这个朋友圈,D-E这个朋友圈,我们发现A,C虽然不是朋友但是他们通过B 相连,在同一个朋友圈内,所以我们查询的时候可以得到A,C属于同一个集合,而A,D不属于同一个集合。

朴素版

void initialize( int Arr[ ], int N) {
    for(int i = 0;i<N;i++)
    Arr[ i ] = i ;//初始化,每个元素的老大都是自己
}
bool find( int Arr[ ], int A, int B) {
    if(Arr[ A ] == Arr[ B ])//A和B的老大相同
        return true;
    else
        return false;   
}
void union(int Arr[ ], int N, int A, int B)
{
    int TEMP = Arr[ A ];
    for(int i = 0; i < N;i++){
        if(Arr[ i ] == TEMP)//把A、B所在的两个集合合并,
        					//所有和A有相同老大的转为拜B的老大为老大
        Arr[ i ] = Arr[ B ]; 
    }
}

这个就是朴素版并查集,我们发现每次合并集合都要遍历所有元素,时间复杂度很大,所以我们需要对其进行优化。

优化1:利用根节点
我们之前用Arr数组存的是元素之间的连通关系,现在我们用Arr表示父节点,即Arr[A]是A的父节点,一直追溯(找祖宗),直到找到Arr[x]=x,即x的父节点是自己,说明这个x就是根节点,所以我们判断A,B是否属于同一个祖宗(根节点),就可以知道他们是否有关系。
(初始化和之前一样)

int root(int Arr[ ],int i){
    while(Arr[ i ] != i) {
        i = Arr[ i ];
    }
    return i;//返回根节点
}
int union(int Arr[ ] ,int A ,int B){
    int root_A = root(Arr, A);       
    int root_B = root(Arr, B);  
    Arr[ root_A ] = root_B ;  //A的根节点改成B的根节点,表示将A,B连通
}
bool find(int A,int B){
    if( root(A)==root(B) )       
        return true;
    else
        return false;
}

优化2:减小树的大小
如上面的代码,我们将每次都直接将A连接到B上,可能会导致最后得到的树很大很乱,而我们每次都将小的集合合并到大的集合上,这样就可以把合并、查找的时间复杂度做到更好。

void weighted-union(int Arr[ ],int size[ ],int A,int B){
    int root_A = root(A);
    int root_B = root(B);
    if(size[root_A] < size[root_B ]){
        Arr[ root_A ] = Arr[root_B];
        size[root_B] += size[root_A];
    }
    else{
        Arr[ root_B ] = Arr[root_A];
        size[root_A] += size[root_B];
    }
}

**优化3:路径亚索压缩 **
上面的方法我们会得到一个很多层的树,每次寻找根节点要循环或者递归寻找,如果数据大的话还是比较耗时的,那么我们可以在每次合并之后,只用一个点做父节点,其他的点全做子节点,即一个折扇一样,父节点好比扇子的转轴,子节点好像扇骨的末端。

int root(int x)
{
    if(arr[x]!=x)
    {
        return x=arr[x]=root(arr[x]);
        //这样就把x到根节点这一条路径上的所有点都直接与父节点相连
        //方便以后查询
    }
    return x;
}

洛谷P3367(并查集模板)
这题对初学者有点不友好,因为需要用到路径压索才能通过,不然只有70分别问我怎么知道的

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double EPS=1e-6;
typedef long long ll;
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n,m;
int arr[N];
int sizes[N];
int root(int x)
{
    if(arr[x]!=x)
    {
        return x=arr[x]=root(arr[x]);
    }
    return x;
}
void uni(int x,int y)
{
    int rootx=root(x);
    int rooty=root(y);
    if(sizes[rooty]>sizes[rootx])
    {
        arr[rootx]=rooty;
        sizes[rooty]+=sizes[rootx];
    }
    else
    {
        arr[rooty]=rootx;
        sizes[rootx]+=sizes[rooty];
    }
}
void finds(int x,int y)
{
    if(root(x)==root(y))
    {
        printf("Y\n");
    }
    else
    {
        printf("N\n");
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        arr[i]=i,sizes[i]=1;
    for(int i=1;i<=m;i++)
    {
        int z,x,y;
        scanf("%d%d%d",&z,&x,&y);
        if(z==1)
        {
            uni(x,y);
        }
        else
        {
            finds(x,y);
        }
    }
}

大一萌新初学,讲述如有不当之处还望指出。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值