并查集及其模板题

并查集的思想

给每个元素一个指针,将其指针指向另外的元素,以此建立一棵“单向的树”(即只可以从下往上溯源的树),所有同一个集合里的元素通过不断溯源都会找到同一个元素,称此元素为“祖宗”。并查集操作可以通过数组实现。

并查集的基本操作

合并

将一个单独的元素并入集合,即将其指针指向集合内任意元素。

查找

对指定元素溯源,可以遍历到此元素到其祖宗路径上的所有元素。

并查集的代码实现

开root数组作为存放每个元素父节点的容器

int root[n+1];

数组root的初始化(将所有元素的初始父节点设置为其本身)

void init(int n)
{
    for(int i=1;i<=n;i++) root[i]=i;
}

合并操作(注意x,y的顺序)
注意输入是否合法,例如先后输入“1 2”和“2 1”,进行合并操作后会形成  1的父亲为2,2的父亲为1,的情况,那么进行查找操作时就会死循环。但是下文优化后的算法不会有此问题。问题的本质是,一棵树有且只有一个祖宗,而祖宗的父节点必须是其本身,因为这查找的判定条件。

void Union(int x,int y)  //传入两个要合并的元素,x是y的父节点
{
    root[y]=x;
}

查找操作(使用递归实现,一直往上溯源父节点,到达祖宗节点后开始返回)

int find(int x)
{
    if(root[x]==x) return x;
    else
    {
        x=find(root[x]);
        return x;
    }
}

 判断操作,判断两个元素是否属于同一个集合,即是否拥有相同的祖宗

bool check(int x,int y)
{
    return find(x)==find(y);
}

 并查集的优化

之前归并元素,是将元素指向直接关联的元素,这样可能会形成一条很长的通往祖宗的路径。对于此情况,可以直接将要归并的元素指向集合的祖宗,此思路只需优化find()函数。

int find(int x)
{
    if(root[x]==x) return x;
    else
    {
        root[x]=find(root[x]);
        return root[x];
    }
}
void Union(int x,int y)
{
    x=find(x);
    y=find(y);
    root[y]=x;
}

注意: 在find函数中去寻找输入元素的祖宗并将祖宗地址回传赋值给路径上每一个节点,使此路径上每个节点直接指向祖宗。但是同理在Union函数中的y所在的路径上的节点指向的是y的祖宗,然后y的祖宗再通过root[y]=x一步指向了x的祖宗,也就是说在y自身不是祖宗的情况下,y到新的祖宗的距离应该是二,即先到y的原祖宗,然后再到现在合并后的祖宗也就是x的祖宗。所以在下文的check函数中,需调用find函数来找祖宗,而不是一步就可以找到的。

check函数

bool check(int x,int y)
{
    return find(x)==find(y);
}

判断x,y是否属于同一集合

例题:文件传输

当两台计算机双向连通的时候,文件是可以在两台机器间传输的。给定一套计算机网络,请你判断任意两台指定的计算机之间能否传输文件?

输入格式:

首先在第一行给出网络中计算机的总数 N (2≤N≤104),于是我们假设这些计算机从 1 到 N 编号。随后每行输入按以下格式给出:

I c1 c2  

其中I表示在计算机c1c2之间加入连线,使它们连通;或者是

C c1 c2    

其中C表示查询计算机c1c2之间能否传输文件;又或者是

S

这里S表示输入终止。

输出格式:

对每个C开头的查询,如果c1c2之间可以传输文件,就在一行中输出"yes",否则输出"no"。当读到终止符时,在一行中输出"The network is connected."如果网络中所有计算机之间都能传输文件;或者输出"There are k components.",其中k是网络中连通集的个数。

输入样例 1:

5
C 3 2
I 3 2
C 1 5
I 4 5
I 2 4
C 3 5
S

输出样例 1:

no
no
yes
There are 2 components.

输入样例 2:

5
C 3 2
I 3 2
C 1 5
I 4 5
I 2 4
C 3 5
I 1 3
C 1 5
S

输出样例 2:

no
no
yes
yes
The network is connected.

代码如下

#include<bits/stdc++.h>
using namespace std;
int a[20001];
void init(int n)
{
    for(int i=1;i<=n;i++) a[i]=i;
}
int find(int x)  //查找x的祖宗同时优化路径上的节点的指针指向
{
    if(a[x]==x) return x;
    else
    {
        a[x]=find(a[x]);
        return a[x];
    }
}
void Union(int x,int y)  //合并操作
{
    x=find(x);
    y=find(y);
    a[y]=x;
}
bool check(int x,int y)  //判断是否连通(是否属于同一集合)
{
    return find(x)==find(y);
}
int main()
{
    int n;
    cin>>n;
    init(n);
    char q;
    int x,y;
    while(cin>>q)  //q判断操作类型
    {
        if(q=='S') break;
        cin>>x>>y;
        if(q=='I') Union(x,y);  //并 操作
        if(q=='C')
        {
            bool f=check(x,y);  //查 操作
            if(f) cout<<"yes"<<endl;
            else cout<<"no"<<endl;
        }
    }
        int sum=0;
        for(int i=1;i<=n;i++)  //求集合个数
        {
            if(a[i]==i) sum++;
        }
        if(sum==1) cout<<"The network is connected."<<endl;
        else cout<<"There are "<<sum<<" components."<<endl;
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值