数据结构--并查集

目录

并查集用法介绍:

基本原理 :

问题:

模板

例题


并查集用法介绍:
  1. 将两个集合合并
  2. 询问两个元素是否在一个集合当中
基本原理 :

每个集合用一棵树来表示。树根的编号就是整个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点。

问题:
  1. 判断树根:if(p[x] == x)(即除了根节点之外,p[x]都不等于x)
  2. 求x的集合编号: while(p[x] != x)     x = p[x];(只要不是树根就一直往上走,最后x就是它的集合编号/树根编号)
  3. 合并两个集合: p[x]是x的集合编号,p[y]是y的集合编号。 p[x]=y(把一个集合的根节点作为另一个集合的儿子)

路径压缩优化时间复杂度,查找集合编号时,把在路径上的儿子直接指向根节点

模板
    (1)朴素并查集:

    int p[N]; //存储每个点的祖宗节点

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);


(2)维护size的并查集:

    int p[N], size[N];
    //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        size[i] = 1;
    }

    // 合并a和b所在的两个集合:
    size[find(b)] += size[find(a)];
    p[find(a)] = find(b);


(3)维护到祖宗节点距离的并查集:

    int p[N], d[N];
    //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);
    d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

作者:yxc
来源:AcWing
//合并集合
void merge_set(int i, int j){
    a = find(i);
    b = find(j);
    if(a != b)   p[a] = b;
}
例题

给定一个包含 n 个点(编号为 1∼n1)的无向图,初始时图中没有边。

现在要进行 m 个操作,操作共有三种:

  1. C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
  2. Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
  3. Q2 a,询问点 a 所在连通块中点的数量;
输入格式

第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 C a bQ1 a b 或 Q2 a 中的一种。

输出格式

对于每个询问指令 Q1 a b,如果 a 和 b 在同一个连通块中,则输出 Yes,否则输出 No

对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量

每个结果占一行。

数据范围

1≤n,m≤1051

输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3
#include<iostream>

using namespace std;

const int N = 100010;

int n,m;
//father数组,存储每个元素的父节点是谁
int p[N];



//寻找根节点并返回
//返回x的祖宗节点+路径压缩
int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);//如果p[x]不是祖宗节点,就让p[x]等于祖宗节点
    return p[x];
}

int main()
{
    //存储集合里有几个节点
    int size[N];
    scanf("%d %d", &n, &m);
    
   
    for(int i = 1; i <= n; i ++)
    {
        p[i] = i;//初始化,每个节点的根节点就是自己
        size[i] = 1;
     }
    
    while(m--)
    {
        char op[2];
        int a, b;
        scanf("%s%d%d", op, &a, &b);//scanf读字符的时候会读入空格,读字符串的时候会自动忽略空格
        //让a的祖宗节点的父亲等于b的祖宗节点
        if(op[0] == 'C')
        {
            scanf("%d%d", &a, &b);
            //判断a,b是否在同一个集合中
            if(find(a) == find(b))    continue;
            
            size[find(b)] += size[find(a)]; 
            //a的根节点的父节点是b的根节点,即把两个不同的集合合并,把集合A作为集合B的儿子
            p[find(a)] = find(b);
            
        }
        else if(op[1] == '1')
        {
            scanf("%d%d", &a, &b);
            //判断两个节点是不是在同一个集合里面
            if(find(a) == find(b))    puts("Yes");
            else    puts("No");
        }
        else
        {
            scanf("%d", &a);
            printf("%d\n", size[find(a)]);
        }
    }
    return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

题号:NC15808
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
64bit IO Format: %lld

题目描述

平面上有若干个点,从每个点出发,你可以往东南西北任意方向走,直到碰到另一个点,然后才可以改变方向。
请问至少需要加多少个点,使得点对之间互相可以到达。

输入描述:

第一行一个整数n表示点数( 1 <= n <= 100)。
第二行n行,每行两个整数xi, yi表示坐标( 1 <= xi, yi <= 1000)。
y轴正方向为北,x轴正方形为东。

输出描述:

输出一个整数表示最少需要加的点的数目。

示例1

输入

复制2 2 1 1 2

2
2 1
1 2

输出

复制1

1

示例2

输入

复制2 2 1 4 1

2
2 1
4 1

输出

复制0

0

 

#include<bits/stdc++.h>
#include<iostream>
 
using namespace std;
const int N = 1010;
int n;
int xy[N][2];
int p[N];
int a, b;
//寻找父节点方法
int find(int x)
{
    if(p[x] != x)    p[x] = find(p[x]);
    return p[x];
}
//合并方法
void meger(int i, int j){
    a = find(i);
    b = find(j);
    if(a != b)   p[a] = b;
}
 
int main()
{
    scanf("%d", &n);
     
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d", &xy[i][0], &xy[i][1]);
        p[i]=i;//父节点初始化
    }
    //遍历每个点寻找x或y相同的点,并合并形成连通块
    for(int i = 1; i <= n; i++){
        for(int j = i + 1; j <= n; j++){
            if(xy[i][0] == xy[j][0] || xy[i][1] ==xy[j][1])//连通条件
            {
                meger(i,  j);//合并成连通块
            }
        }
    }
    int ans = 0;
    //记录有多少个连通块
    for(int i = 1; i <= n; i++){
        if(p[i] == i){
            ++ans;
        }
    }
    printf("%d\n", ans - 1);
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lmmqxyxyixiu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值