寒假算法训练——并查集

目录

并查集作用

并查集思想

合并操作

查找操作

优化:路径压缩

基本思想:

实现方式:

例题一: 

例题二:


并查集作用

1、将两个集合合并。

2、查找两个元素是否在同一个集合中。

并查集思想

1、 每个集合用一棵树表示,集合的编号就是根节点的编号,假如我现在有4个集合

{1,2,3, 4},初始时这四个节点都只有自己,即只有根节点,如下图。

合并操作

将两个不相交的集合合并为一个集合,在并查集中实质为将其中一个元素的根节点(代表性元素)指向另外一个集合的根节点(代表性元素)。

现在我们进行以下几个操作:

这里集合下标都是从1开始的,如初始:index = 1,value = 1,代表每个下标的根节点都为自己

1、合并(2,4),即将集合2合并到集合4里面(需要将2指向4,即把2的父亲节点变为4),合并后:1,4,3,4,其中index  = 2 ,value = 4,集合2的根节点指向集合4的根节点,如下图。

2、合并(4,3),合并后:1,4,3,3,index = 4,value = 3,理由同上,如下图。

 3、合并(1,3),合并后,3,4,3,3,index = 1,value = 3,如下图。

查找操作

这里Find表示一个集合,Find(x) = 父亲节点的值

1、Find [1] = 3

2、Find [2] = 4 

3、Find [3] = 3

显然,如果Find [x] = x的话,那么x为根,在写代码中,如果我们要查找元素x的根节点为多少,只需要while(Find [x] != x)x = Find [x]。

例如,我们查找2的根节点:

x = 2,Find [2] = 4,因为 Find [2] != 2,所以 x = 4 = Find[2]

x = 4,  Find [4] = 3,因为 Find [4] != 4,所以 x = 3 = Find [4]

x = 3,Find [3] = 3,因为 Find [3] == 3,所以退出循环,最终x为3,即2的根节点为3,同理2属于集合3。

优化:路径压缩

路径压缩的关键是将路径上的每个节点的父节点设为根节点,以减少树的深度。这样做可以保证后续的查找操作在常数时间内完成,提高了并查集的整体效率。

基本思想:
  1. 查找操作时的路径压缩: 在执行查找操作时,递归或迭代地找到元素所在集合的根节点。

  2. 将路径上的每个节点的父节点直接设为根节点: 在查找的过程中,将路径上经过的每个节点的父节点都直接设为根节点,使得整个路径上的节点都直接连接到根节点,从而降低树的深度。

实现方式:

1、递归方式:

在递归方式中,查找的同时进行路径压缩。

int find(int x) {
    if (a[x] == x) return x;
    return a[x] = find(a[x]); // 路径压缩
}

2、迭代方式:

在迭代方式中,使用循环进行查找,然后再进行路径压缩。

int find(int x) {
    int root = x;
    while (a[root] != root) {
        root = a[root];
    }
    
    // 路径压缩
    while (a[x] != root) {
        int temp = a[x];
        a[x] = root;
        x = temp;
    }
    
    return root;
}

如下图,分别为路径压缩前与路径压缩后。

路径压缩前:

路径压缩后:

此时查找2的根节点不用再通过2->4->3,直接便可以2->3,这便让并查集这个算法时间复杂度近似到O(1),接下来我们通过两个实际的例题来演示算法。

例题一: 

这道题来自Acwing的一道算法基础题,链接:836. 合并集合 - AcWing题库,代码如下。

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5+10;
int a[N];
int find(int x)
{
   if(a[x] == x) return a[x];
   else return a[x] = find(a[x]);//这里写成return find(a[x])的话就没有路径压缩,可能会超时
}
int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i = 1;i<=n;i++)
    {
        a[i] = i;
    }//初始化每个集合的根节点为他本身
    for(int i = 0;i<m;i++)
    {
        char c;
        int x,y;
        cin>>c>>x>>y;
        if(c == 'M')
        {
            if(find(x) != find(y)) a[find(y)] = find(x);//这里写成了a[y] = x
        }
        else
        {
            if(flag(x) == flag(y)) cout<<"Yes"<<endl;
            else cout<<"No"<<endl;
        }
    }
    return 0;
}

例题二:

这道题来自PTA L2-010 排座位,链接:PTA | 程序设计类实验辅助教学平台 (pintia.cn)

代码如下:

#include <iostream>
#include <vector>

using namespace std;

const int MAXN = 105;
int parent[MAXN];  // 记录每个宾客的父节点
int relation[MAXN][MAXN];  // 记录每个宾客的关系,1表示朋友,-1表示敌对

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

void unite(int x, int y) {
    int root_x = find(x);
    int root_y = find(y);
    if (root_x != root_y) {
        parent[root_x] = root_y;
    }
}

int main() {
    int N, M, K;
    cin >> N >> M >> K;

    for (int i = 1; i <= N; ++i) {
        parent[i] = i;  // 初始化每个宾客的父节点为自己
    }

    for (int i = 0; i < M; ++i) {
        int x, y, relationship;
        cin >> x >> y >> relationship;
        relation[x][y] = relationship;
        relation[y][x] = relationship;
        if(relationship == 1) unite(x,y);
    }

    for(int i = 0;i<K;i++)
    {
        int x,y;
        cin>>x>>y;
        if(relation[x][y] == 1) cout<<"No problem"<<endl;
        else if(relation[x][y] == 0) cout<<"OK"<<endl;
        else if(relation[x][y] == -1 && find(x)==find(y)) cout<<"OK but..."<<endl;
        else cout<<"No way"<<endl;
    }
}

这里并查集的作用就是查找是否有共同的朋友,即根节点是否相同,例如两个人的关系为敌人的话(-1)如果根节点相同,即有共同的朋友,输出 Ok but ...

本文到此结束,后期有需要补充的地方我会持续补充,有不懂的可以评论区留言,寒假我会持续更新,大家一起加油!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值