• 并查集—汇总


    先吐槽一下,在循环压缩路径时,若采取以下代码,路径压缩之后 树的深度有2,明明深度只增加一层,却直接超时了,运行时间直接增加了至少10倍 ◔ ‸◔?

    int find(int x)
    {
        int a = x;
        while (p[x] != -1)
            x = p[x];
        while (p[a] != x)  //这里改了
        {
            int t = p[a];
            p[a] = x;
            a = t;
        }
        return x;
    }

    现在进入正题: 

    目前,学了并查集学了一周多了,想着总结一下自己所学,感觉并查集虽然不是属于很复杂的数据类型,却也是设计的很精妙,可以运用于许多地方。

    一,先讲一下 parent 数组的初始化为,我开始学的时候,是全部初始化为-1 ,但是学长们都是初始化为 p[i]=i

    做到现在,也没感觉有什么差别,只是 find 这个函数,-1的话,注意不要查询到 根节点的父节点了,就好了。

    另外还有一点,就是初始化为 -1 的话,无法用递归压缩路径,因为 你最后返回不是根结点。

    题目:http://www.fjutacm.com/Problem.jsp?pid=2592

    初始化为 -1  的代码

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #define N 1000000+10
    int p[N], n, m;
    int find(int x)
    {
        int a = x;
        while (p[x] != -1)
            x = p[x];
        while (p[a] != -1)
        {
            int t = p[a];
            p[a] = x;
            a = t;
        }
        return x;
    }
    int Find(int x)
    {
        if (p[x] != x)
            p[x] = find(p[x]);
        return p[x];
    }
    void join(int x, int y)
    {
        x = find(x), y = find(y);
        if (x == y)
            return;
        p[x] = y;
    }
    int main(void)
    {
        //while (scanf("%d%d", &n, &m) != EOF)
        scanf("%d%d", &n, &m);
        {
            for (int i = 0; i <= n + 5; i++)
                p[i] = -1;
    
            while (m--)
            {
                int a, b;
                scanf("%d%d", &a, &b);
                int ans = find(a);
                if (ans <= b)
                {
                    printf("%d
    ", ans);
                    p[ans] = ans + 1;
                }
                else
                    puts("-1");
    
            }
        }
    
        system("pause");
        return 0;
    }
    View Code

    二,可以利用根节点记录 集合中元素的个数,集合中最大或最小的数值,这样可以利用压缩路径,减少查询时间

    另外,集合的个数可以利用 是否成环的个数 进行计算

    三,集合的区域划分(自己命名的 )

    题目:http://www.fjutacm.com/Problem.jsp?pid=2351

    题目:http://www.fjutacm.com/Problem.jsp?pid=2022

    由于并查集是根据数据的编号进行 join 的,这样的话若是 编号重复了(T^T的图论)或者 编号与编号的对应含义不同(食物链)

    于是我们可以在数据范围之外再开一组,

    如:T^T的图论

    ,要标记出现的行列,只需要 x join y 就可以了,但是他们的数据出现了重叠

    于是 我们可以让 0~n 给 x, n~2n 给 y,然后 x join y+n ,就解决了数据的重复意义。

    // 文件名出现 T^T的图论   出现这个 ^ 直接找不到路径打不开,好坑
    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    using namespace std;
    int a[500005], p[100005];
    int Find(int x)
    {
        return p[x] == x ? x : p[x] = Find(p[x]);
    }
    int find(int x)
    {
        int a = x;
        while (x != p[x])
            x = p[x];
        while (a != x)
        {
            int t = p[a];
            p[a] = x;
            a = t;
        }
        return x;
    }
    void join(int x, int y)
    {
        x = find(x), y = find(y);
        if (x != y)
            p[x] = y;
    }
    int main(void)
    {
        int n;
        while (scanf("%d", &n) != EOF)
        {
            for (int i = 1; i <= 100000; i++)
                p[i] = i;
    
            int b;
            for (int i = 1; i <= n; i++)
            {
                scanf("%d%d", &a[i], &b);
                join(a[i], b + 50000);
            }
    
            int flag = 1;
            for (int i = 2; i <= n; i++)
            {
                if (find(a[i]) != find(a[i - 1]))
                {
                    flag = 0;
                    break;
                }
            }
            if (flag)
                puts("YES");
            else
                puts("NO");
        }
        return 0;
    }
    View Code

    如:食物链 ,

    你要 join 两个点,但这两个点却有四种含义关系,直接 join  的话,你又怎么知道是什么关系

    于是我们可以分三个区间,这样就有 四种区别了

    我们默认

    同一区间的为同类

    第一区间 吃 第二区间

    第二区间 吃 第三区间

    第三区间 吃 第一区间

    这样的话,

    find(x): 找 x 的同类

    find(x+n): 找 x 的猎物

    find(x+2*n): 找 x 的猎物的猎物,同时也是 x 的天敌

    同时

    x 与 y 同类 就有 三种情况

        join(x, y);
        join(x + n, y + n);
        join(x + 2 * n, y + 2 * n);

    x 吃 y 也有三种情况

     join(x + n, y);               // x 的猎物是 y
        join(x, y + 2 * n);         // x 的天敌是 y
        join(x + 2 * n, y + n);   // x的天敌 是 y的猎物

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #define N 50000+5
    int p[N * 3];   // g++ 不能这样?只能 int p[200000+5] 或 p[(50000+5)*3]
                    // 同类,猎物,天敌
    int find(int x)
    {
        int a = x;
        while (x != p[x])
            x = p[x];
        while (x != a)
        {
            int t = p[a];
            p[a] = x;
            a = t;
        }
        return x;
    }
    void join(int x, int y)
    {
        x = find(x), y = find(y);
        ++++++++++++++++++++++++++
        if (x == y)
            return;
        p[x] = y;
    }
    int main(void)
    {
        int n, k;
        scanf("%d %d", &n, &k);
        {
            for (int i = 1; i <= n * 3 + 2; i++)
                p[i] = i;
    
            int ans = 0;
            while (k--)
            {
                int d, x, y;
                scanf("%d %d %d", &d, &x, &y);
                if (x > n || y > n)
                {
                    ans++; continue;
                }
                if (d == 1)
                {
                    // x 的猎物是 y  或 x 的天敌是 y
                    if (find(x + n) == find(y) || find(x + 2 * n) == find(y))
                    {
                        ans++;
                        continue;
                    }
                    // x 的同类是 y
                    join(x, y);
                    join(x + n, y + n);
                    join(x + 2 * n, y + 2 * n);
                }
                else  // x 吃 y
                {
                    if (x == y)
                    {
                        ans++;
                        continue;
                    }
                    // x 的同类是 y 或者 y 的猎物是 x
                    if (find(x) == find(y) || find(x) == find(y + n))
                    {
                        ans++; continue;
                    }
    
                    join(x + n, y);    // x 的猎物是 y
                    join(x, y + 2 * n);   // x 的天敌是 y
                    join(x + 2 * n, y + n);   // x的天敌 是 y的猎物 
                }
            }
            printf("%d
    ", ans);
        }
        system("pause");
        return 0;
    }
    View Code

    四,压缩路径

    通过压缩路径减少查询时间

    题目:http://www.fjutacm.com/Problem.jsp?pid=2592

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #define N 1000000+10
    int p[N], n, m;
    int find(int x)
    {
        int a = x;
        while (p[x] != x)
            x = p[x];
        while (a != x)
        {
            int t = p[a];
            p[a] = x;
            a = t;
        }
        return x;
    }
    int Find(int x)
    {
        if (p[x] != x)
            p[x] = find(p[x]);
        return p[x];
    }
    void join(int x, int y)
    {
        x = find(x), y = find(y);
        if (x == y)
            return;
        p[x] = y;
    }
    int main(void)
    {
        //while (scanf("%d%d", &n, &m) != EOF)
        scanf("%d%d", &n, &m);
        {
            for (int i = 0; i <= n + 5; i++)
                p[i] = i;
    
            while (m--)
            {
                int a, b;
                scanf("%d%d", &a, &b);
                int ans = Find(a);
                if (ans <= b)
                {
                    printf("%d
    ", ans);
                    p[ans] = ans + 1;
                }
                else
                    puts("-1");
    
            }
        }
        
        system("pause");
        return 0;
    }
    View Code

    题目:http://www.fjutacm.com/Problem.jsp?pid=2650

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #define N 2*100000+5
    struct vessel
    {
        int c;  // 容量
        int w;   // 现在水的体积
    }v[N];
    int p[N];
    int find(int x)
    {
        int a = x;
        while (x != p[x])
            x = p[x];
        while (p[a] != x)
        {
            int t = p[a];
            p[a] = x;
            a = t;
        }
        return x;
    }
    void scan(int &x)
    {
        char c;
        do c = getchar();
        while (c<'0' || c>'9');
        x = c - '0';
        while ('0' <= (c = getchar()) && c <= '9') x = x * 10 + c - '0';
    }
    
    void print(int a)
    {
        if (a>9)
            print(a / 10);
        putchar(a % 10 + '0');
    }
    
    int main(void)
    {
        int n; scan(n);
        for (int i = 1; i <= n; i++)
        {
            scan(v[i].c);
            v[i].w = 0;
            p[i] = i;
        }p[n + 1] = n + 1, v[n + 1].c = 2147483647 - 1, v[n + 1].w = 0;
    
    
        int t; scan(t);
        while (t--)
        {
            int num; scan(num);
            if (num == 1)        
            {
                int x, y;
                scan(x); scan(y);
    
                x = find(x);
                if (v[x].w + y < v[x].c)
                    v[x].w += y;
                else
                    while(v[x].w + y >= v[x].c)
                    {
                        int q = find(x + 1);
                        if (q != n + 1)
                            v[q].w += (v[x].w + y - v[x].c);
                        v[x].w = v[x].c;
    
                        p[x] = q;
                        x = find(x), y = 0;
                    }
            }
            if (num == 2)
            {
                int x; scan(x);
                print(v[x].w); puts("");
            }
        }
        return 0;
    }
    View Code

    五,还有个移动次数的

    见:https://www.cnblogs.com/asdfknjhu/p/12528457.html

    六,并查集还可以和其他东西结合

    并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题

    =========== ========= ======== ======= ====== ====== ===== === == =

      《沙扬娜拉》  徐志摩

              ——赠日本女郎

      最是那一低头的温柔,

      像一朵水莲花不胜凉风的娇羞,

      道一声珍重,道一声珍重,

      那一声珍重里有蜜甜的忧愁——

  • 相关阅读:
    《JavaScript高级程序设计》扩展关于动态原型
    “三角边”的那点事儿
    请允许我说:数学是如此美丽!
    球面模型分解及仿flash标签云
    关于Javascript模块化和命名空间管理
    我所了解的关于JavaScript定义类和对象的几种方式
    正因为我们是前端,所以代码更需要优雅
    “作弊”还是创意?伪3D
    mybatis常用jdbcType数据类型以及对应的JavaType
    mdx之计算成员和命名集
  • 原文地址:https://www.cnblogs.com/asdfknjhu/p/12566090.html
Copyright © 2020-2023  润新知