数据结构-并查集(21.7.20)

我们之前简单了解过、栈、队列、链表等一些基本的数据结构,之前做题的时候用的少,掌握不熟练。因为时间紧迫,那就一边进行高级的数据结构,然后穿插的熟练一下之前的吧。

并查集,用于处理一些不相交集合的合并问题。从名字可以看出来主要包含两部分,合并 find( ) 和查找 join( )。

基本原理: 将所有对象划分为几个不相交集合,每个集合利用一棵树来表示,树根的编号就是整个集合的编号,每个结点存储它的父节点,用一个数组来储存。

几个步骤:
1、初始化 2、合并 3、查找 4、统计集的个数

重要操作步骤:
1、判断树根: if(p[x] == x)
2、求x的集合编号: while(p[x] != x) x=p[x] (一般会用一个递归函数来写)
3、合并两个集合: p[x]=y(p[y]=x)将两个集合的根部合并(如果两个根节点的高度不同,要把高度较小的集合并到较大的集上,能减少树的高度)
4、路径压缩优化: 通常查询元素 i 所属的集需要搜索路径找到根节点,返回的结果是根节点,路径很长,如果在返回的时候顺便把 i 所属的集改成根节点,方便下次搜索的时候节省时间。

int find(int x)//原始
{ 
    return x==p[x]?x:find(p[x]);
} 

int find (int x)//路径压缩
{
    if(p[x] != x)  p[x] = find(p[x]);
    return p[x];
}

做了两个题便于熟悉和掌握
1、很基础很基础
https://ac.nowcoder.com/acm/problem/14685
给你一个 n 个点,m 条边的无向图,求至少要在这个的基础上加多少条无向边使得任意两个点可达~
输入描述:
第一行两个正整数 n 和 m 。
接下来的m行中,每行两个正整数 i 、 j ,表示点i与点j之间有一条无向道路。

原理:题目每次会给出两个点,我们就需要把这两个点的根节点设置为相同的,并且这两个点一定不需要重新连

代码:
int p[100005];
void set()//初始化
{
    for(int i=1;i<100005;i++)
        p[i]=i;
}
int find(int x)//查找函数
{
    if(p[x]!=x)  p[x]=find(p[x]);
    return p[x];
}
int main()
{
    int n,t,m,sum=0,a,b;
    set();
    cin>>n>>m;
    while(m--)
    {
        cin>>a>>b;
        a=find(a);//找大树根
        b=find(b);
        if(a!=b)
        {
            p[a]=b;
            sum++;//不需要重新连的个数
        }
    }
    cout<<n-1-sum<<endl;
}

2、
https://ac.nowcoder.com/acm/problem/15808
平面上有若干个点,从每个点出发,你可以往东南西北任意方向走,直到碰到另一个点,然后才可以改变方向。
请问至少需要加多少个点,使得点对之间互相可以到达。
输入描述:
第一行一个整数n表示点数( 1 <= n <= 100)。
第二行n行,每行两个整数xi, yi表示坐标( 1 <= xi, yi <= 1000)。
y轴正方向为北,x轴正方形为东。
输出描述:
输出一个整数表示最少需要加的点的数目。
在这里插入图片描述

原理:二维数组原理也一样,把这一点看作一个元素,然后判断下一个点与他是否直接相连,相连就修改父节点,一定与原来值不相同,如果未修改过,那他就是最终点,我们最后判断可以连成几串即可

代码:
int p[100005];
struct aa
{
    int x,y;
}a[105];
void set()
{
    for(int i=1;i<=1005;i++)
    {
        p[i]=i;
    }
}
int find(int x)//查找,路径压缩优化
{
    if(p[x]!=x)  p[x]=find(p[x]);
    return p[x];
}
int main()
{
    int n,t,m,sum=0;
    set();
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i].x>>a[i].y;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;j++)
        {
            if(a[i].x==a[j].x||a[i].y==a[j].y)
            {
                int ii=find(i);
                int jj=find(j);
                p[ii]=jj;
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(p[i]==i) sum++;//未修改过的,有几个就是几个不相关的集合
    }
    cout<<sum-1<<endl;
}

小总结:
先写这些,弄懂原理真是一件及其麻烦的事,就做了两个简单应用的题,便于我理解原理
通过这俩题,看出来并查集通常会解决一个关系圈的问题,也就是朋友的朋友也是我的朋友,最顶端的朋友和别的顶端如果是朋友,那他们所联系的所有朋友又都互相是朋友,由小圈又连成了大圈,问题也围绕着共有几个圈,或者这几个圈需要几步才能连在一起等等。
其实研究完之后,觉得学习并查集之前的我应该也会想到将两个有联系的点合并成一个,但是如果要实现的话会非常的麻烦,学习了并查集给了我一个简单化的模板,嗯~挺好用的吧

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值