数据结构与算法之并查集-3

75 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第20天,点击查看活动详情

✨欢迎关注🖱点赞🎀收藏⭐留言✒

🔮本文由京与旧铺原创,csdn首发!

😘系列专栏:java学习

💻首发时间:🎞2022年12月10日🎠

🀄如果觉得博主的文章还不错的话,请三连支持一下博主哦

🎧作者是一个新人,在很多方面还做的不好,欢迎大佬指正,一起学习哦,冲冲冲

🎀🎀🎀今日分享:这一年大概是我长这么大,最难熬的一年,也是让我成长最多的一年,感谢生活赐予我一场惊慌失措,但愿以后抬头阳光

🐱‍💻导航小助手

3.小试牛刀: 合并集合

3.1题目详情

题目链接: 836. 合并集合

一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。

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

  1. M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;

输入格式

第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 M a bQ a b 中的一种。

输出格式

对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1n,m1051≤n,m≤10^5

输入样例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例:

Yes
No
Yes

3.2解体思路

本题的意思就是会给你n个数和一些操作,需要将这些数合并集合或查询是否再同一个集合,其实就是并查集的一个实现,实现思路都已经介绍过了,下面给出代码。

3.3解题代码

java版本:

import java.util.*;
​
class UnionFindSet {
    private int[] p;
    
    public UnionFindSet(int cap) {
        p = new int[cap + 1];
        //将所有的元素都指向自己
        for (int i = 1; i <= cap; i++) {
            p[i] = i;
        }
    }
    
    //重置数组
    public void init() {
        for (int i = 1; i < p.length; i++) {
            p[i] = i;
        }
    }
    
    //查找某个元素的根结点
    public int find(int x) {
        if (x != p[x]) p[x] = find(p[x]);
        return p[x];
    }
    //合并两个集合
    public void union(int a, int b) {
        //第一步,找根
        int ar = find(a);
        int br = find(b);
        
        //如果是同一个集合,不用合并
        if (ar == br) return;
        
        //将b集合与a集合合并
        p[br] = ar;
    }
}
​
class Main {
    private static final int N = (int) 1e5 + 233;
    private static UnionFindSet un = new UnionFindSet(N);
    
    private static int n, m;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        sc.nextLine();
        //初始化
        un.init();
        while (m-- > 0) {
            String s = sc.nextLine();
            String[] ss = s.split(" ");
            char op = ss[0].charAt(0);
            int a = Integer.parseInt(ss[1]);
            int b = Integer.parseInt(ss[2]);
            if (op == 'M') {
                //合并
                un.union(a, b);
            } else if (op == 'Q') {
                if (un.find(a) == un.find(b)) System.out.println("Yes");
                else System.out.println("No");
            }
        }
    }
}

c++版本:

#include <iostream>
#include <algorithm>
#include <string>
​
using namespace std;
​
const int N = (int) 1e5 + 233;
​
int n, m;
​
class UnionFindSet 
{
private:
    int* p;
    int _cap;
​
public:
    //构造函数
    UnionFindSet(int cap) 
    {
        p = (int*) malloc(sizeof(int) * (cap + 1));
        _cap = cap;
        //将所有元素初始化
        for (int i = 1; i <= cap; i++)
        {
            p[i] = i;
        }
    }
    
    //将元素重置
    void clear() 
    {
        for (int i = 1; i < _cap; i++) 
        {
            p[i] = i;
        }
    }
​
    //查找某元素的根
    int find(int x) 
    {
        if (x != p[x]) p[x] = find(p[x]);
        return p[x];
    }
​
    //union合并两个集合
    void _union(int a, int b)
    {
        int ar = find(a);
        int br = find(b);
        
        if (ar == br) return;
        
        p[br] = ar;
    }
};
​
​
int main()
{
    static UnionFindSet un(N);
    cin >> n >> m;
    //初始化并查集
    un.clear();
    
    while (m-- > 0)
    {
        char op;
        int a, b;
        cin >> op >> a >> b;
        
        if (op == 'M')
        {
            //合并
            un._union(a, b);
        } else if (op = 'Q')
        {
            if (un.find(a) == un.find(b)) cout << "Yes" << endl;
            else cout << "No" << endl;
        }
    }
    
    return 0; 
}

在竞赛当中,一般直接使用数组实现,而不会先实现一个类,实现思路是一样的,我就不具体介绍了,详细代码如下:

java版本:

import java.util.*;
​
class Main {
    private static final int N = (int) 1e5 + 233;
    private static int[] p = new int[N];
    private static int n, m;
    private static int find(int x) {
        if (x != p[x]) p[x] = find(p[x]);
        return p[x];
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        sc.nextLine();
        //初始化
        for (int i = 0; i < N; i++) {
            p[i] = i;
        }
        while (m-- > 0) {
            String s = sc.nextLine();
            String[] ss = s.split(" ");
            char op = ss[0].charAt(0);
            int a = Integer.parseInt(ss[1]);
            int b = Integer.parseInt(ss[2]);
            if (op == 'M') {
                //合并
                p[find(b)] = find(a);
            } else if (op == 'Q') {
                if (find(a) == find(b)) System.out.println("Yes");
                else System.out.println("No");
            }
        }
    }
}

c++版本:

#include <iostream>
#include <algorithm>
#include <string>
​
using namespace std;
​
const int N = (int) 1e5 + 233;
​
//并查集数组
int p[N];
int n, m;
​
//查找并查集的根节点
int find(int x) 
{
    if (x != p[x]) p[x] = find(p[x]);
    
    return p[x]; 
}
​
int main()
{
    cin >> n >> m;
    //初始化并查集
    for (int i = 0; i < N; i++)
    {
        p[i] = i;
    } 
    
    while (m-- > 0)
    {
        char op;
        int a, b;
        cin >> op >> a >> b;
        
        if (op == 'M')
        {
            //合并
            p[find(b)] = find(a);
        } else if (op = 'Q')
        {
            if (find(a) == find(b)) cout << "Yes" << endl;
            else cout << "No" << endl;
        }
    }
    
    return 0; 
}

数据结构并查集的内容差不多就是这些了。


力扣中的并查集:

1971. 寻找图中是否存在路径 547. 省份数量 990. 等式方程的可满足性 1020. 飞地的数量 765. 情侣牵手