算法篇之并查集

本文深入浅出地介绍了并查集的基本概念、实现方法及其在解决实际问题中的应用。通过具体的例子,如杭电1232畅通工程问题,详细解释了如何使用并查集进行路径压缩,提高查询效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


并查集顾名思义合并,查找集合。是对不相交集合的一种灵活处理方式。

并查集的处理方式

1:初始化,把每个点所在集合初始化为其自身;

2:查找元素所在集合,即查找元素根节点;

3:合并,即把两个元素所在集合合并为一个集合。合并两个不相交集合并判断两个元素是否属于一个集合;

就杭电1232畅通工程这题分析一下

题目链接http://acm.hdu.edu.cn/showproblem.php?pid=1232

  首先在地图上给你若干个城市,这些城市可以看做点,然后告诉你哪些城市质检室有道路直接连接的,最后要解决的是整幅地图的联通问题。

举个栗子,随意给你两个点,让你判断他们是否联通,或者问你整幅图有几个连通分支,即问你这幅图被跟成了几个互相独立的块,即抽象一点说成集合。。

 回到畅通工程这题,问你还需要修几条路,就是让你判断有几个联通分支。如果有一个连通分支,那么说明地图上的点都已经被连起来了,所以要修的路就是0条;如果有两个连通分支,那就是要再修一条路,从这两个分支中任选一点,把他们连接起来那么所有点都是连起来的了;如果是三个。。。。

  那给出第一组数据来说4 2 1 3 4 3;有四个城市,两条路,城市1城市3相连,城市4城市3相连,那么整幅图就被分成了1-3-4与2这两部分,所以有两个连通分支,只要把城市2跟其他任意一个点连起来那么这个整个图就是联通的了,而输出结果是1。好了,那么当有好多城市有好多路的时候我们该怎么做呢?铛铛铛铛~好用的并查集登场了

  首先按照一般的并查集处理步骤,先将每个点所在集合初始化为其自身,我用pre【maxn】数组来保存每个点的父节点。然后调用查找函数

int pre[1000 ];
int find(int x) //查找根节点
{
int r=x;
while ( pre[r ] != r ) //返回根节点 r
r=pre[r ];
 
int i=x , j ;
while( i != r ) //路径压缩
{
j = pre[ i ]; // 在改变上级之前用临时变量 j 记录下他的值
pre[ i ]= r ; //把上级改为根节点
i=j;
}
return r ;
}

找到集合的根节点之后呢,就到了我们并查集的第三部:合并,再调用一次join函数

void join(int x,int y) //判断x y是否连通,
//如果已经连通,就不用管了 //如果不连通,就把它们所在的连通分支合并起,
{
int fx=find(x),fy=find(y);
if(fx!=fy)
pre[fx ]=fy;
}
 

好了并查集现在就说完了~什么你问我靠!说好的好理解呢?!那我们下面说一个更有爱的栗子。俗话说人在江湖飘谁能不挨刀,行走江湖就要随时做好被砍的准备,为了不被砍死我们就要广交朋友嘛,毕竟多了一个朋友就少了一个回来砍我们的人~当然我们江湖中人讲究的就是一个义字,首先我们不会砍朋友!其次本着朋友的朋友就是我的朋友的原则,所以朋友的朋友我们也不会砍。但是呢,遇到朋友还好说,毕竟都是见过的,要是遇到朋友的朋友,一不小心我们干起来了,然后有人快被砍死了才发现我靠你是那谁谁的人啊?我靠我也是,不好意思这次砍伤了你,我们就此别过,下次不会了吐舌头,呵呵。。砍错一次人之后呢,你就会学乖了,下次动手前先问一遍,你哪一派的啊?对方说我少林的,你一想哎呀我干爹还在少林寺干过呢,那不行,是自己人,咱们就握手言和不打了。朋友少还好说,万一朋友上万了,打之前还要不断问一遍你上级,你认识那谁嘛?

上级说,不认识啊,我再问问我大哥,大哥又问问他掌门。。。。这样一天下来,大哥大说不认识,打!人早跑了~所以呢就有了我们并查集中的路径压缩~

int find(int x) //查找我(x)的掌门
{
int r=x; //委托 r 去找掌门
while (pre[r ]!=r) //如果r的上级不是r自己(也就是说找到的大侠他不是掌门 = =)
r=pre[r ] ; // r 就接着找他的上级,直到找到掌门为止。
int i=x , j ;
while( i != r ) //路径压缩
{
j = pre[ i ]; // 在改变上级之前用临时变量 j 记录下他的值
pre[ i ]= r ; //把上级改为根节点
i=j;}//把每个分支都变成掌门的直系手下
return r ; //掌门驾到~~~
}

现在大家明白了吧~

附上杭电1232这题代码

#include<stdio.h>
#include<string.h>
int n,m;
int p[1005];
int find(int x)
{
    int g = x,h;
    while(p[x] != x)
    x = p[x];
    while(p[g] != x)
    {
        h = p[g];
        p[g] = x;
        g = h;
    }
    return     x;
}
int main(void)
{
    int i,j,k;
    int la,lb;
    int c;
    while(scanf("%d",&n)!=EOF)
    {
        if(n == 0)
        return 0;
        scanf("%d",&m);
        c = n;
//开始n个联通分支        
        for(i = 1; i <= n; i++)
        p[i] = i;
        for( i = 1; i <= m; i++)
        {
            scanf("%d%d",&j,&k);
            la = find(j);
            lb = find(k);
            if(la != lb)
            {
                p[la] = lb;
                c--;//每次合并一个就减少一个联通分支
            }
        }
        printf("%d\n",c-1);

    }
    return 0;
}

并查集博大精深,一定多做些题目学会好好运用他!


 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值