并查集学习笔记

一、基本概念

并查集(Union-Find)是一种用于处理不相交集合的合并和查询问题的数据结构。它主要用于处理一些不交集的合并及查询问题,例如网络中的连通分量、图的边联通性等。

这种数据结构支持两种操作:查找(Find)和合并(Union)。

并查集通常使用一个整数数组parent[]来表示,其中parent[i]表示元素i的父节点。如果parent[i] = i,则i是一个根节点,即它是集合的代表。

并查集的主要操作有:

  1. Find: 查找元素所在的集合,即集合的代表。
  2. Union: 将两个元素所在的集合合并为一个集合。

二、并查集的扩展算法

  1. 路径压缩:
    为了提高效率,路径压缩技术应运而生。路径压缩是在执行Find操作时实现的,其目的是使得从任何一个节点到根节点的路径尽可能短,从而加快未来的查找速度。
  2. 按秩合并:
    另一种优化技术是按秩合并(Union by Rank)。秩可以理解为树的高度,我们总是将更矮的树连接到更高的树上。这样可以避免树过深,从而导致操作效率降低。

三、C++实现

以下是一个使用路径压缩和按秩合并的并查集的C++实现:

#include <iostream>
#include <vector>

class UnionFind {
private:
    std::vector<int> parent; // 存储父节点
    std::vector<int> rank;   // 秩(可以理解为树的高度)

public:
    // 构造函数,初始化并查集
    UnionFind(int size) : parent(size), rank(size, 0) {
        for (int i = 0; i < size; i++) {
            parent[i] = i; // 初始时,每个元素的父节点是它自己
        }
    }

    // 查找操作,带路径压缩
    int find(int p) {
        if (parent[p] != p) {
            parent[p] = find(parent[p]); // 路径压缩
        }
        return parent[p];
    }

    // 合并操作,按秩合并
    void unionSets(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP != rootQ) { // 只有根节点不同,才需要合并
            if (rank[rootP] > rank[rootQ]) {
                parent[rootQ] = rootP; // 小树接到大树下面
            } else if (rank[rootP] < rank[rootQ]) {
                parent[rootP] = rootQ; // 小树接到大树下面
            } else {
                parent[rootQ] = rootP; // 如果相同,任选一个作为根,并增加其秩
                rank[rootP]++;
            }
        }
    }
};

int main() {
    UnionFind uf(10); // 创建大小为10的并查集
    uf.unionSets(1, 2);
    uf.unionSets(3, 4);
    uf.unionSets(1, 4); // 合并1/2集合与3/4集合

    // 输出元素1的代表
    std::cout << "The representative of element 1 is: " << uf.find(1) << std::endl;
    // 输出元素3的代表
    std::cout << "The representative of element 3 is: " << uf.find(3) << std::endl;

    return 0;
}

代码解释
UnionFind 类:包含两个主要数组,parent和rank。parent数组用于跟踪每个元素的父节点,而rank数组记录了每个树的秩(或高度)。

Find 函数:实现了路径压缩。这是通过递归将元素直接链接到其根节点来实现的。路径压缩确保了后续的查找操作更加高效。

Union 函数:按秩合并两个集合。较低秩的根节点将指向较高秩的根节点。如果两棵树的秩相同,选择其中一个树作为根,并增加其秩。

这种结构和操作方法显著提高了并查集处理大量数据和查询的效率。

四、应用场景

并查集是一种非常有效的数据结构,用于处理一些不交集的合并及查询问题。这里有几个具体的应用场景:

  1. 网络连接:并查集可以用来检查网络中的计算机是否相互连接。例如,在局域网或社交网络中,可以用并查集来快速找出两台计算机是否通过某种方式连接。
  2. 图的连通分量:在处理无向图时,可以用并查集来识别和计算图中的连通分量数量。每个连通分量可以用一个集合来表示,合并操作可以帮助快速将两个节点连在一起。
  3. 最小生成树:在计算最小生成树的Kruskal算法中,可以用并查集来检测添加的边是否会形成环路。这是通过检查边的两个节点是否已经在同一集合中来实现的。
  4. 动态连通性:在需要多次添加和查询操作的动态网络中,如在实时系统中动态地添加路由和查询路由信息,可以利用并查集的动态连通性检查功能。
  5. 图像处理:在图像处理中,可以使用并查集来进行区域标记或计算每个连通区域的大小,特别是在处理二值图像时,用来识别和标记相连的组件。
  6. 数学中的等价关系:并查集可以用来维护和查询元素之间的等价关系,如在抽象代数、集合理论中的等价类划分。
  7. 路径问题:在某些路径优化问题中,如迷宫的路径寻找或最短路径问题,可以用并查集快速判断两点之间是否存在路径。

这些应用场景展示了并查集在多种领域的实用性和效率,特别是在需要频繁执行合并集合和查询集合代表元素的操作时。

五、例题

力扣721.账户合并

给定一个列表 accounts,每个元素 accounts[i] 是一个字符串列表,其中第一个元素 accounts[i][0] 是 名称 (name),其余元素是 emails 表示该账户的邮箱地址。

现在,我们想合并这些账户。如果两个账户都有一些共同的邮箱地址,则两个账户必定属于同一个人。请注意,即使两个账户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的账户,但其所有账户都具有相同的名称。

合并账户后,按以下格式返回账户:每个账户的第一个元素是名称,其余元素是 按字符 ASCII 顺序排列 的邮箱地址。账户本身可以以 任意顺序 返回。

示例 1:

输入:accounts = [[“John”, “johnsmith@mail.com”, “john00@mail.com”], [“John”, “johnnybravo@mail.com”], [“John”, “johnsmith@mail.com”, “john_newyork@mail.com”], [“Mary”, “mary@mail.com”]]
输出:[[“John”, ‘john00@mail.com’, ‘john_newyork@mail.com’, ‘johnsmith@mail.com’], [“John”, “johnnybravo@mail.com”], [“Mary”, “mary@mail.com”]]
解释:
第一个和第三个 John 是同一个人,因为他们有共同的邮箱地址 “johnsmith@mail.com”。
第二个 John 和 Mary 是不同的人,因为他们的邮箱地址没有被其他帐户使用。
可以以任何顺序返回这些列表,例如答案 [[‘Mary’,‘mary@mail.com’],[‘John’,‘johnnybravo@mail.com’],
[‘John’,‘john00@mail.com’,‘john_newyork@mail.com’,‘johnsmith@mail.com’]] 也是正确的。
示例 2:
输入:accounts = [[“Gabe”,“Gabe0@m.co”,“Gabe3@m.co”,“Gabe1@m.co”],[“Kevin”,“Kevin3@m.co”,“Kevin5@m.co”,“Kevin0@m.co”],[“Ethan”,“Ethan5@m.co”,“Ethan4@m.co”,“Ethan0@m.co”],[“Hanzo”,“Hanzo3@m.co”,“Hanzo1@m.co”,“Hanzo0@m.co”],[“Fern”,“Fern5@m.co”,“Fern1@m.co”,“Fern0@m.co”]]
输出:[[“Ethan”,“Ethan0@m.co”,“Ethan4@m.co”,“Ethan5@m.co”],[“Gabe”,“Gabe0@m.co”,“Gabe1@m.co”,“Gabe3@m.co”],[“Hanzo”,“Hanzo0@m.co”,“Hanzo1@m.co”,“Hanzo3@m.co”],[“Kevin”,“Kevin0@m.co”,“Kevin3@m.co”,“Kevin5@m.co”],[“Fern”,“Fern0@m.co”,“Fern1@m.co”,“Fern5@m.co”]]
提示:
1 <= accounts.length <= 1000
2 <= accounts[i].length <= 10
1 <= accounts[i][j].length <= 30
accounts[i][0] 由英文字母组成
accounts[i][j] (for j > 0) 是有效的邮箱地址

题解:

class UnionFind{
    public:
    vector<int> parent;
    UnionFind(int n){
        parent.resize(n);
        for(int i=0;i<n;i++)
        {
            parent[i] = i;
        }
    }
    void UnionSet(int index1,int index2){
        parent[find(index2)] = find(index1);
    }
    int find(int index){
        if(parent[index]!=index){
            parent[index] = find(parent[index]);
        }
        return parent[index];
    }
};
class Solution {
public:
    vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
            map<string,int> EmailToIndex;
            map<string,string> EmailToName;
            int EmailCount=0;
            for(auto& account:accounts){
                string& name = account[0];
                int size = account.size();
                for(int i=1;i<size;i++){
                    if(!EmailToIndex.count(account[i])){
                        string& email = account[i];
                        EmailToIndex[email] = EmailCount++;
                        EmailToName[email] = name;
                    }
                }
                
            }
            UnionFind uf(EmailCount);
            for(auto& account:accounts){
                string& FirstEmail = account[1];
                int FirstEmailIndex = EmailToIndex[FirstEmail];
                int size = account.size();
                for(int i=2;i<size;i++)
                {
                    string& NextEmail = account[i];
                    int NextEmailIndex = EmailToIndex[NextEmail];
                    uf.UnionSet(FirstEmailIndex,NextEmailIndex);
                }
            }
            map<int,vector<string>> IndexToEmail;
            for(auto&[email,_]:EmailToIndex){
                int index = uf.find(EmailToIndex[email]);
                vector<string>& account = IndexToEmail[index];
                account.emplace_back(email);
                IndexToEmail[index] = account;
            }
            vector<vector<string>> marge;
            for(auto& [_,email]:IndexToEmail){
                sort(email.begin(),email.end());
                string& name = EmailToName[email[0]];
                vector<string> account;
                account.emplace_back(name);
                for(auto& emails:email){
                    account.emplace_back(emails);
                }
                marge.emplace_back(account);
            }
            return marge;
    }
};

六、总结

并查集是一种高效的数据结构,专门用于处理不相交集合的合并和查询问题。它通过一种称为“路径压缩”的优化技术来快速寻找一个元素的根节点,同时利用“按秩合并”的策略来减少查找路径的长度,从而在实际应用中达到近乎常数时间的操作效率。

并查集广泛应用于多种场景,如网络连接的快速检测、图的连通分量分析、最小生成树的构建、动态连通性问题、图像处理中的连通区域标记等。在这些应用中,它主要处理组件连接性的问题,使得用户能够高效地查询和合并节点集合。

因此,并查集不仅是理解计算机科学中图论和网络算法的一个重要工具,也是优化数据处理和增强程序性能的关键技术之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值