并查集

发布时间 2023-11-27 10:57:27作者: 加固文明幻景

并查集

概述

  • 并查集是一种树形数据结构,经常用于处理一些集合之间的操作,例如元素查找,集合合并。
  • 不同集合在并查集中以不同的树表示,一般每棵树的根节点会作为当前集合的代表元
  • 想要查询两个元素是不是在同一集合中,只需要比较两个元素所在集合的代表元是否相同即可。

实现

  • 初始化

  • const int N = 100010;
    int fa[N];//记录每个元素由谁代表
    int sz[N];//记录每个集合元素个数
    int dep[N + 1];//记录每个集合树的深度
    
  • 假设有 \(n\) 个元素,这些元素初始都是独立的。显然它们构成了 \(n\) 个集合,每个集合的代表元就是元素自己。

  • void initalize(int n)
    {
    	for (int i = 1; i <= n; i++)
        {
    		fa[i] = i;
            sz[i] = dep[i] = 1;
        }
    }
    
  • 将两个元素 \(x\)\(y\) 所在的集合合并

    • 先找到 \(x\)\(y\) 对应的代表元。(即 \(\text{fa}\) 等于自己的元素)

    • 将其中一个代表元的 \(\text{fa}\) 指向另外一个,那么在这个代表元下的所有元素都会指向另外一个。

    • int findSet(int x)
      {
      	if (fa[x] == x)
          {
      		return x;
          }
          return findSet(fa[x]);
      }
      void Union(int x, int y)
      {
      	int fx = findSet(x), fy = findSet(y);
          if (fx == fy)
          {
      		return ;
          }
          fa[fx] = fy;//把fx的代表元改为fy
      }
      
  • 路径压缩

    • 如果并查集在合并中形成一条长链,每次查找代表元都可能要花费大量时间。

    • 我们可以缩短并查集中的路径,具体做法就是在查询的过程中,把沿途的每个节点的 \(\text{fa}\) 都设为集合代表元。

    • 当有 \(n\) 个元素和 \(m\) 次查询时,时间复杂度 \(\operatorname O(m\log n)\)

      • 1                          1
        |                        / | \
        2                       2  3  4
        |
        3
        |
        4
        
    • int findSet(int x)
      {
      	if (x == fa[x])
          {
              return x;
          }
          fa[x] = findSet(fa[x]);//路径压缩
          return fa[x];
      }
      
    • //简写
      int findSet(int x)
      {
          return x == fa[x] ? x : (fa[x] = findSet(fa[i]));
      }
      
  • 启发式合并

    • 在合并集合的时候,我们尽量选择包含元素个数少的集合把它并入另一个集合中,使需要改变代表元的元素的数量尽可能少。

    • 当有 \(n\) 个元素和 \(m\) 次查询时,时间复杂度 \(\operatorname O(m\log n)\)(跟路径压缩没差,其实可以用路径压缩)

    • void Union(int x, int y)
      {
          int fx = findSet(x), fy = findSet(y);
          if (fx == fy)
          {
              return ;
          }
          if (sz[fx] > sz[fy])
          {
      		swap(fx, fy);
          }
          fa[fx] = fy;
          sz[fy] += sz[fx];
      }
      
  • 按深度合并

    • 在合并集合的时候,我们尽量选择深度较小的集合把它并入另一个集合中。

    • 在路径压缩的时候,有可能会破坏维护的深度值,但算法总体复杂度不会变差

    • void Union(int x, int y)
      {
          int fx = findSet(x), fy = findSet(y);
          if (fx == fy) return;
          if (dep[fx] > dep[fy]) swap(fx, fy);
          fa[fx] = fy;
          if (dep[fx] == dep[fy]) dep[fy]++;
      }
      

例题

  • P1551 亲戚

    • 模板题

    • #include<iostream>
      #include<algorithm>
      #include<cstdio>
      
      const int N = 5e3 + 10;
      int n, m, p;
      int fa[N];
      
      int findSet(int x);
      
      void Union(int x, int y)
      {
      	int fx = findSet(x), fy = findSet(y);
      	if (fx == fy) return;
      	fa[fx] = fy;
      }
      
      int findSet(int x)
      {
      	if (x == fa[x])
      	{
      		return x;
      	}
      	fa[x] = findSet(fa[x]);
      	return fa[x];
      }
      
      void initalize(int n)
      {
      	for (int i = 1; i <= n; i++)
      	{
      		fa[i] = i;
      	}
      }
      
      int main()
      {
       	std::ios::sync_with_stdio(false);
      	std::cin.tie(nullptr);
      	std::cout.tie(nullptr);
      	std::cin >> n >> m >> p;
      	initalize(n);
      	for (int i = 1; i <= m; i++)
      	{
      		int x, y;
      		std::cin >> x >> y;
      		Union(x, y);
      	}
      	for (int i = 1; i <= p; i++)
      	{
      		int x, y;
      		std::cin >> x >> y;
      		if (findSet(x) == findSet(y))
      		{
      			std::cout << "Yes\n";
      		}
      		else
      		{
      			std::cout << "No\n";
      		}
      	}
      	return 0;
      }
      
  • 统计连通块

    • 这类题统计连通块个数就是统计 \(fa\) 数组等于其本身的个数。

    • 即集合数量。

    • P8654 蓝桥杯 2017 国 C 合根植物

    • #include<iostream>
      #include<algorithm>
      #include<cstdio>
      
      int fa[1000010];
      int m, n;
      int vis[1000010];
      int ans;
      
      int findSet(int x)
      {
      	if (x == fa[x])
      	{
      		return x;
      	}
      	fa[x] = findSet(fa[x]);
      	return fa[x];
      }
      
      void Union(int x, int y)
      {
      	int fx = findSet(x), fy = findSet(y);
      	if (fx == fy)
      	{
      		return ;
      	}
      	fa[fx] = fy; 
      }
      
      int main()
      {
       	std::ios::sync_with_stdio(false);
      	std::cin.tie(nullptr);
      	std::cout.tie(nullptr);
      	std::cin >> m >> n;
      	int k;
      	std::cin >> k;
      	for (int i = 1; i <= n * m; i++)
      	{
      		fa[i] = i;
      	}
      	for (int i = 1; i <= k; i++)
      	{
      		int a, b;
      		std::cin >> a >> b;
      		Union(a, b);
      	}
      	for (int i = 1; i <= n * m; i++)
      	{
      		if (fa[i] == i) ans++;
      	}
      	std::cout << ans;
      	return 0;
      }
      
    • P1536 村村通

    • #include<iostream>
      #include<algorithm>
      #include<cstdio>
      
      int n, m;
      int fa[1010];
      
      void initalize(int n)
      {
      	for (int i = 1; i <= n; i++)
      	{
      		fa[i] = i;
      	}
      }
      
      int findSet(int x)
      {
      	if (fa[x] == x)
      	{
      		return x;
      	}
      	fa[x] = findSet(fa[x]);
      	return fa[x];
      }
      
      void merge(int x, int y)
      {
      	int fx = findSet(x), fy = findSet(y);
      	if (fx == fy) return;
      	fa[fx] = fy;
      	return ;
      }
      
      int main()
      {
       	std::ios::sync_with_stdio(false);
      	std::cin.tie(nullptr);
      	std::cout.tie(nullptr);
      	while(std::cin >> n >> m)
      	{
      		if (!n) break;
      		initalize(n);
      		while(m--)
      		{
      			int x, y;
      			std::cin >> x >> y;
      			merge(x, y);
      		}
      		int cnt = 0;
      		for (int i = 1; i <= n; i++)
      		{
      			if (fa[i] == i)
      			{
      				cnt++;
      			}
      		}
      		std::cout << cnt - 1 << std::endl;
      	}
      	return 0;
      }
      
  • P1892 BOI2003 团伙