并查集

并查集(Union-Find)

参考博客

wiki

解决的问题

  • 并查集解决的是一个 连接问题

  • 并查集支持两个操作:

    • union(p,q) 并! 两个节点所在的集合合并
    • find(p) 查! 查询根节点
  • 回答一个问题:

    • isConneted(p,q) : 两个节点是否连通.

    只需给出是否连通. 不需要给出具体路径.

建模思路

  • 对于所有连通的节点. 认为他们是一个组. 因此不连通的节点必然不在同一个组.

  • 将所有的节点用整数表示.即对于N个节点.使用0到N-1的整数来表示.

    注意其中使用整数来表示节点

    如果需要使用其他的数据类型表示节点

    比如使用字符串,那么可以用哈希表来进行映射

    即将String映射成这里需要的Integer类型

  • 初始化时.各自为一个组. 组号 = 索引号.即对于节点i.它的组号也是i

动态连通图有几种可能的操作

  • 查询节点所属的组

    数组对应位置的值即为组号

  • 判断两个节点是否属于同一个组

    分别得到两个节点的组号,然后判断组号是否相等

  • 连接两个节点,使之属于同一个组

    分别得到两个节点的组号,组号相同时操作结束,不同时,将其中的一个节点的组号换成另一个节点的组号

  • 获取组的数目

    初始化为节点的数目,然后每次成功连接两个节点之后,递减1

Quick-Find 算法

  • 用数组保存每个节点的组号.
namespace UF1 {
    class  UnionFind {

    private:
        int *id; // 表示组号
        int count; // 一共有多少个节点
    public:
        UnionFind(int n) {
            count = n;
            id = new int[n];
            // 初始化为各自一个组.
            for (int i = 0; i < n; i++) {
                id[i] = i;
            }
        }
        ~UnionFind() {
            delete[]id;
        }

        // 并操作 O(n)
        void unionElements(int p, int q) {
            // 找个各自的组号.
            // 若相等无需操作.若不等.找到所有组号为pID的节点.将其改成qID
            int pID = find(p);
            int qID = find(q);
            if (qID == pID)
                return;
            for (int i = 0; i < count; i++) {
                if (id[i] == pID)
                    id[i] = qID;
            }
        }
        // 返回组号
        int find(int p) {
            assert(p >= 0 && p < count);
            return id[p];
        }
        // 判断是否连通
        bool isConnected(int p, int q) {
            return find(p) == find(q);
        }
    };
}

Quick-Find 存在的问题

  • 在查询的时,只需要一次操作就可以获得某个节点的组号.
  • 但是在并操作时候,需要遍历整个数组.进行修改组号.

Quick-Union 算法

  • Quick-Find 每个节点各自记录自己的组号. 牵一发而动全身!!!
  • 通过 树形结构 . 每个节点记录自己的父亲节点. 根节点即为所属的组. 这样,合并操作就变成了. 将两个数合并到一起.
  • find 查操作 需要查询到根节点.

// 每个节点自己的父亲节点.
namespace UF2 {
    class UnionFind
    {
    private:
        int *parent;
        int count;
    public:
        UnionFind(int n) {
            parent = new int[n];
            count = n;
            for (int i = 0; i < count; i++) {
                parent[i] = i; // 初始化每个节点单独一个组.自己指向自己.
            }
        }
        ~UnionFind()
        {
            delete[] parent;
        }

        int find(int p) {
            assert(p >= 0 && p < count);
            // 若不是根节点. p=其父亲节点.
            while (parent[p] != p) {
                p = parent[p];
            }
            return p;
        }

        bool isConnected(int p, int q) {
            return find(p) == find(q);
        }

        void unionElements(int p, int q) {
            // 查询两个节点根节点. 
            // 若相等. 则不需操作.
            // 若不等.则将某个根节点指向另一个根节点.完成合并.
            int pRoot = find(p);
            int qRoot = find(q);
            if (pRoot == qRoot)
                return;
            parent[qRoot] = pRoot;
        }
    };
}

Quick-Union 存在的问题

  • union 并操作的时候. 是按固定的顺序将两个节点的根节点合并.可能导致树的层数变得很多.
  • 改进1: 增加一个 sz数组 . 其保存以i节点作为根节点的数的节点个数.
  • 在合并时候,将 sz[i] 小的指向 sz[i] 大的.
// sz[i] 表示以元素i为根的集合中的元素个数
namespace UF3 {
    class UnionFind
    {
    private:
        int *parent;
        int count;
        int *sz; // sz[i] 表示以元素i为根的集合中的元素个数
    public:
        UnionFind(int n) {
            parent = new int[n];
            sz = new int[n];
            count = n;
            for (int i = 0; i < count; i++) {
                parent[i] = i;
                sz[i] = 1; // 初始化为1
            }

        }
        ~UnionFind()
        {
            delete[] parent;
            delete[] sz;
        }

        int find(int p) {
            assert(p >= 0 && p < count);
            while (parent[p] != p) {
                p = parent[p];
            }
            return p;
        }

        bool isConnected(int p, int q) {
            return find(p) == find(q);
        }

        void unionElements(int p, int q) {
            int pRoot = find(p);
            int qRoot = find(q);
            if (pRoot == qRoot)
                return;
            // 判断根节点的sz谁大.大的作为根节点.
            // 并维护sz
            if (sz[pRoot] < sz[qRoot]) {
                parent[pRoot] = qRoot;
                sz[qRoot] += sz[pRoot];
            }
            else {
                parent[qRoot] = pRoot;
                sz[pRoot] += sz[qRoot];
            }
        }
    };
}

sz数组的问题

  • sz数组 : sz[i] 表示以元素i为根的集合中的元素个数

  • 有些数可能节点数量多.. 但是它真实的高度并不高.

  • 在合并时. 重要的是判断两个数的高度.

  • 将 sz数组改成rank数组

    rank[i] 表示以元素i为根的树的高度

// rank[i] 表示以元素i为根的树的高度
namespace UF4 {
    class UnionFind
    {
    private:
        int *parent;
        int count;
        int *rank; // rank[i] 表示以元素i为根的树的高度
    public:
        UnionFind(int n) {
            parent = new int[n];
            rank = new int[n];
            count = n;
            for (int i = 0; i < count; i++) {
                parent[i] = i;
                rank[i] = 1; // 初始化为1
            }

        }
        ~UnionFind()
        {
            delete[] parent;
            delete[] rank;
        }

        int find(int p) {
            assert(p >= 0 && p < count);
            while (parent[p] != p) {
                p = parent[p];
            }
            return p;
        }

        bool isConnected(int p, int q) {
            return find(p) == find(q);
        }

        void unionElements(int p, int q) {
            int pRoot = find(p);
            int qRoot = find(q);
            if (pRoot == qRoot)
                return;

            if (rank[pRoot] < rank[qRoot]) {
                parent[pRoot] = qRoot;
            }
            else if(rank[pRoot] > rank[qRoot]){
                parent[qRoot] = pRoot;
            }
            else { //rank[pRoot] == rank[qRoot] 需要维护高度
                parent[pRoot] = qRoot;
                rank[qRoot] += 1;
            }
        }
    };
}

路径压缩!!!

  • find 查询操作进行优化.
// 如父亲节点和该节点不相等.
// 则将将节点指向其爷爷节点.
// 再将p赋值成为其父亲节点(即原来的爷爷节点) 继续向上搜索.
int find(int p) {
    assert(p >= 0 && p < count);
    // 连跳两级
    while (parent[p] != p) {
        parent[p] = parent[parent[p]];
        p = parent[p];
    }
    return p;
}
  • 递归优化
    将p节点直接挂到其根节点下.
// 路径压缩1
int find2(int p) {
    if (parent[p] != p) {
        parent[p] = find2(parent[p]);
    }
    return parent[p];
}
AlgorithmConstructorUnionFind
Quick-FindNN1
Quick-UnionNTree heightTree height
Weighted Quick-UnionNlgNlgN
Weighted Quick-Union With Path CompressionNVery near to 1 (amortized)Very near to 1 (amortized)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值