并查集(详细篇)

并查集的分析

并查集是一种非常精巧而且实用的数据结构,它主要用于处理一些不相交集合合并的问题,经典例子有联通子图,最小生成树之克鲁斯卡尔算法和最近公共祖先等…

常见操作

  • 合并两个集合,将一个集合的根节点作为另一个集合的根节点
  • 查找某个元素属于哪个集合
  • 判断两个元素是否属于同一集合,只需要判断他们的根节点是否相同即可


代码
并查集代码一般有三个函数,他们分别是

  • 初始化函数初始化每个点为独立的集合,一般来说初始化都是写在主函数里面
    for (int i = 1; i <= n; ++i)
            pre[i] = i;
  • 查找函数,查找一个元素所在的集合,沿着父节点一直找下去,直到找到根节点为止
int find(int x)//路径压缩
{
    if (x != pre[x])
    //如果当前节点不等于父节点那么就递归查找,知道找到为止,并把根节点赋给当前节点实现路径压缩功能
        pre[x] = find(pre[x]);
    return pre[x];
}
  • 合并函数,将一个集合的根节点指向另一个集合的根节点
void join(int x, int y)
{
    int a = find(x);
    int b = find(y);
    if (a != b)
        pre[a] = b;//让b等于a的父节点
}

这里强调一下,如果没有返回值那么函数设为void型,有时候使用int定义函数但是又没有返回值会报错



并查集例题

题目描述

在某个城市中住着n个人,现在给定关于这n个人的m条信息(即某2个人认识)。
假设所有认识的人一定属于同一个单位,请计算该城市有多少个单位?
输入
第1行的第1个值表示总人数n,第2个值表示总信息数m;第2行开始为具体的认识关系信息
输出
单位的个数

样例输入
10 4
2 3
4 5
4 8
5 8

样例输出
7

这是一道并查集模板题,见代码~

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int dp[1010][1010];
int pre[maxn];
int m, n;
int find(int x)
{
   if (x != pre[x])
      pre[x] = find(pre[x]);
   return pre[x];
}
void join(int x, int y)
{
   int a = find(x);
   int b = find(y);
   if (a != b)
      pre[a] = b;
}
int main()
{
   while (~scanf("%d%d", &n, &m))
   {
      int ans = 0;
      for (int i = 1; i <= n; ++i)
         pre[i] = i;
      for (int i = 1; i <= m; ++i)
      {
         int x, y;
         scanf("%d%d", &x, &y);
      //   if (find(x) != find(y))
            join(x, y);
      }
      for (int i = 1; i <= n; ++i)
         if (pre[i] == i)//去查找一共还剩下几个集合
            ans++;
      printf("%d\n", ans);
   }
   return 0;
}

hdu1213

杭电1213传送门
这也是一道经典的并查集的模板题,与上题几乎一样,由于我们初始化的时候把每个点都设为独立的集合,之后我们又进行了合并的操作,那么我们就可以看看有哪些点的父节点没有变,有几个那么就有几个集合

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
int pre[maxn];
int find(int x)
{
   if (x != pre[x])
      pre[x] = find(pre[x]);
   return pre[x];
}
void join(int x, int y)
{
   int a = find(x);
   int b = find(y);
   if (a != b)
      pre[a] = b;
}
int main()
{
   int t;
   scanf("%d", &t);
   while (t--)
   {
      int ans = 0;
      int n, m, x, y;
      scanf("%d%d", &n, &m);
      for (int i = 1; i <= n; ++i)
         pre[i] = i;
      for (int i = 1; i <= m; ++i)
      {
         scanf("%d%d", &x, &y);
         join(x, y);
      }
      for (int i = 1; i <= n; ++i)
         if (pre[i] == i)
            ans++;
      printf("%d\n", ans);
   }
   return 0;
}

poj1611

poj1611传送门

这也是一道比较经典的例题,只不过稍微有些变形,其实大概思路还是一样,由于poj是一个十分古老的在线oj,所以它不支持万能头文件~
在我们把该合并的合并之后,我们只需要差找哪些节点与0号患病者在一个集合就行,同样也是一句代码解决

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1e5 + 10;
int dp[1010][1010];
int s[maxn];
int pre[maxn];
int m, n;
int find(int x)
{
    if (x != pre[x])
        pre[x] = find(pre[x]);
    return pre[x];
}
void join(int x, int y)
{
    int a = find(x);
    int b = find(y);
    if (a != b)
        pre[a] = b;
}
int main()
{
    while (~scanf("%d%d", &n, &m))
    {
        if (n == 0 && m == 0)
            break;
        int ans = 0, cnt;
        for (int i = 0; i <= n; ++i)
            pre[i] = i;
        while (m--)
        {
            int x, y;
            scanf("%d", &cnt);
            scanf("%d", &x);
            for (int i = 1; i < cnt; ++i)
            {
                scanf("%d", &y);
                join(x, y);
            }
        }
        for (int i = 0; i < n; ++i)
            if (find(0) == find(i))
                ans++;
        printf("%d\n", ans);
    }
    return 0;
}
凡有告别,便有重逢,今天的离别只是为了明天更好的再聚~
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值