zoukankan      html  css  js  c++  java
  • 【详解】并查集高级技巧:加权并查集、扩展域并查集

    一、普通并查集

      可以理解为使用数组实现的树形结构,只保存了每个节点的父节点(前驱)。

      功能为:合并两个节点(及其所在集合) 、 查找节点所属集合的代表节点(可以理解为根节点)。

    原理及用法

    以6个元素为例(编号0到5):把0单独划分为一个集合;把1,2,3,4划分为一个集合;把5单独划分为一个集合。

      1. 初始化  init()

      n个元素的并查集,只需要一个容量为n的数组f[n],值全部初始化为自己即可:for(int i=0;i<n;i++) f[i]=i;

      2. 查找节点所属集合  Find(x)

      主要代码:Find(x):  if(x == f[x]) return x;

                  return Find(f[x]);

      但若只是简单的这样做,会出现上图第三个圆中的情况,即查找某个节点时递归太多次。因此需要“路径压缩”,只需增加一步:

           Find(x):  if(x == f[x]) return x;

                  return f[x] = Find(f[x]);

      3. 合并两个节点(及其所在集合)  Union(x, y)

        Union(x,y):  int fx=Find(x), fy=Find(y);

               if(fx != fy) f [fx] = fy;  // 此处换为f [fy] = fx也行,道理相同,意义和效果其实也一样。

      注意:一定是f [fx] = fy,而不是f [x] = y。只有把x和y的最终父节点(前驱)连接起来,所属的两个集合才算真正完全连通,整个逻辑也才能正确。

    二、扩展域并查集

    使用情景:

      n个点有m对关系,把n个节点放入两个集合里,要求每对存在关系的两个节点不能放在同一个集合。问能否成功完成?

    思路:

      把每个节点扩展为两个节点(一正一反),若a与b不能在一起(在同一个集合),则把a的正节点与b的反节点放一起,把b的正节点与a的反节点放一起,这样就解决了a与b的冲突。若发现a的正节点与b的正节点已经在一起,那么说明之前的某对关系与(a,b)这对关系冲突,不可能同时满足,即不能成功完成整个操作。

    具体实现:

      1. 初始化  init()

      n个点,每个点扩展为两个点(一正一反),则需要一个容量为2*n的数组f[n],值全部初始化为自己即可:for(int i=0;i<2*n;i++) f[i]=i;

      (注意初始编号,若编号为[1,n],则初始化应该为:for(int i=1;i<=2*n;i++) f[i]=i;)

          一个点x的正点编号为x,反点编号为x+n(这样每个点的反点都是+n,规范、可读性强、不重复、易于理解)。

      2.  Find(x)和Union(x, y)不需要修改,含义和实现不变。

      3. 解决问题的算法步骤

      1)初始化2*n个节点的初始父节点,即它本身。

      2)遍历m对关系,对每对(a,b),先找到a和b的父节点,若相等则说明(a,b)的关系与之前的关系有冲突,不能同时解决,则得到结果:不能完成整个操作。

          否则执行:Union(a, b+n), Union(b, a+n).  (这时已经Find过了,直接执行f [fx] = fy这一句就等效与Union(x, y) )

      3)若m对关系都成功解决,则得到结果:能够完成整个操作。

    拓展:

      由于扩展域会直接使数组容量翻倍,所有一般只解决这种“二分”问题,只扩展为2倍即可。

      优点在于:结构简单,并查集的操作也不需要做改变,非常易于理解。  缺点显然就是:需要额外存储空间。

    三、加权并查集

    使用情景:

      N个节点有M对关系(M条边),每对关系(每条边)都有一个权值w,可以表示距离或划分成多个集合时的集合编号,问题依然是判断是否有冲突或者有多少条边是假的(冲突)等。

    思路:

      给N个节点虚拟一个公共的根节点,增加一个数组s[n]记录每个节点到虚拟根节点的距离,把x,y直接的权值w看为(x,y)的相对距离。

      Union(x,y,w)时额外把x,y到虚拟根节点的距离(s值)的相对差值设置为w;Find(x)时,压缩路径的同时把当前s值加上之前父节点的s值,得到真实距离。

    具体实现:

      1. 初始化  init()

      f[n]数组记录节点的父节点,s[n]数组记录节点到虚拟根节点的距离:  for(int i=0;i<n;i++) {  f[i]=i;  s[i]=0; }

      2.  Find(x)

          if(x==f[x])return x;

          int t  = f[x];

          f[x] = Find(f[x]);

          s[x] += s[t];

          // s[[x] %= mod;  若s[x]表示划分成mod个集合时的集合编号等情况时,则需要求余。

          return f[x];

      3. Union(x, y,w)

          int fx = Find(x), fy = Find(y);  //此时已经s[x]和s[y]都已经计算为真值。

          if(fx != fy) {

            f [fx] = fy;

            s [fx] = (s[x] - s[y] + w + mod) % mod;

          }

      4. 解决问题的算法步骤

        初始化后,遍历m对关系:若x,y的父节点不同,则Union(x,y,w);否则,若x与y的差值为w,则说明正确,继续遍历,不为w时说明出现冲突。

        当s[x]只是代表划分为mod个集合时的集合编号时,应该比较s[x]与s[y]的值是否相同,相同时说明出现冲突;不相同时说明之前已经解决了,正确可继续遍历。

    拓展:加权并查集主要得赋予并理解s[x]值的意义,较难掌握且应用广泛

      牛客网例题:关押罪犯 https://ac.nowcoder.com/acm/problem/16591 ,里面的题解和讨论区有更多讲解和入门题目链接

      直接百度搜素“加权并查集”也可找到更多讲解和入门题目链接。

    牛客网关押罪犯的题解代码:

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    
    const int maxn = 20002;
    const int maxm = 100002;
    
    struct edge {
        int a, b, c;
    }e[maxm];
    
    bool cmp(edge a, edge b) {
        return a.c > b.c;
    }
    
    int f[2 * maxn];
    int Find(int x) {
        if (x == f[x])return x;
        return f[x] = Find(f[x]);
    }
    
    int main() {
        int n, m;
        scanf("%d%d", &n, &m);
        for (int i = 0; i < m; i++)
            scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);
        sort(e, e + m, cmp);    //按仇恨值从大到小排序
        for (int i = 1; i <= 2 * n; i++)f[i] = i;    //初始化并查集
    
        int i;    //从大到小依次把每对罪犯安排到不同监狱
        for (i = 0; i < m; i++) {
            int a = Find(e[i].a), b = Find(e[i].b);
            if (a == b)break;    //两人的正点已在同一个集合,无法解决,最大冲突出现
            f[a] = Find(e[i].b + n);    //把a和b的反点(敌人)合并
            f[b] = Find(e[i].a + n);    //把b和a的反点(敌人)合并(每个点都有一个正点和反点)
        }
        if (i == m)printf("0");
        else printf("%d", e[i].c);
        return 0;
    }
    扩展域的并查集解法
    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const int maxn = 200000 + 10;
    const int gxs = 2;    //模2就只有0,1两个值,分别代表两个不同的集合
    const int mod = 2;
    int n, m;
    int f[maxn], s[maxn];    //f记录父节点(前驱),s记录到虚拟root点的距离
    
    void init() {
        for (int i = 0; i < maxn; i++) f[i] = i, s[i] = 0;
    }
    //查找
    int finds(int x) {
        if (x == f[x]) return x;
        int t = f[x];
        f[x] = finds(f[x]);
        s[x] += s[t];    //s[x]原来是与t的相对距离,现在是与root的相对距离
        s[x] %= gxs;    //s值求余后代表所属监狱(二选一)
        return f[x];
    }
    
    //新建关系
    void unions(int x, int y, int w) {
        int fx = finds(x), fy = finds(y);
        if (fx != fy) {
            f[fy] = fx;
            s[fy] = s[x] - s[y] + w + gxs;    //相对距离设置为w,解决这一对冲突
            s[fy] %= mod;        //求余直接赋予实际意义:所属的mod个集合的编号
        }
    }
    
    struct node {
        int a, b;
        ll val;
        bool operator < (const node &a)const {
            return val > a.val;
        }
    };
    
    vector<node> que;
    
    int main() {
        cin >> n >> m;
        int a, b;
        ll v;
        for (int i = 0; i < m; i++) {
            cin >> a >> b >> v;
            que.push_back(node{ a,b,v });
        }
        sort(que.begin(), que.end());    //从大到小排序
        init();
        for (int i = 0; i < m; i++) {
            a = que[i].a;
            b = que[i].b;
            v = que[i].val;
            if (finds(a) == finds(b)) {    //在同一个集合就不能直接解决冲突
                if (s[a] == s[b]) {            //若s值相同就说明已经在同一个集合,冲突无法解决
                    cout << v << endl;        //因为从大到小遍历,第一个解决不了的关系的val就是答案:最小化的最大冲突值
                    return 0;
                }                            //否则说明解决之前的冲突后,当前冲突也被解决。
            }
            else {        //不在一个集合就可以通过设置s值解决冲突
                unions(a, b, 1);
            }
        }
        cout << 0 << endl;
        return 0;
    }
    加权并查集解法
  • 相关阅读:
    redis php 实例
    redis 常用操作命令
    Django2.2集成xadmin管理后台所遇到的错误集锦,解决填坑
    软件测试理论知识点
    网络七层协议模型、TCP/IP四层模型
    UDP和TCP有什么区别
    HTTP与HTTPS的区别
    mysql的主键和索引
    mysql 事务未提交导致死锁 Lock wait timeout exceeded; try restarting transaction 解决办法
    chmod命令详细用法
  • 原文地址:https://www.cnblogs.com/zsh-notes/p/12820467.html
Copyright © 2011-2022 走看看