Disjoint Set

Disjoint Set,最基本的操作就是 Union 和 Find 两个函数。

Union 有根据size和rank两种方法,而 Find 通常使用 path compression 来提升后续搜索的效率。

时间复杂度等可以参考 http://web.eecs.utk.edu/~plank/plank/classes/cs302/Notes/Disjoint/

 

实现一:建立parent数组,这样做实现起来非常简单,但是也只适用于集合元素是从0开始自然数(比如一个数组下标)。

Union 根据size合并。

#include <iostream>

class DisjointSet{
public:
    DisjointSet(int size):parent(size,-1){}
    
    void Union(int x, int y){
        int root1=Find(x), root2=Find(y);
        if (root1==root2) return;
        
        // whoever's size is bigger becomes parent of other
        // notice parent[root] is negative, if parent[root] is smaller, then its size is larger
        if (parent[root1]>parent[root2]){
            parent[root2] += parent[root1];
            parent[root1] = root2;
        }else{
            parent[root1] += parent[root2];
            parent[root2] = root1;
        }
    }
    
    int Find(int x){
        if (parent[x]<0) return x;
        return parent[x] = Find(parent[x]);
    }
    
private:
    vector<int> parent;
};

int main() {
    DisjointSet s(10);
    s.Union(2,1);
    cout << s.Find(1);
    return 0;
}

 

实现二:创建一个结构体Node,成员是val和指向parent的指针。相比上一种方法的好处是,val没有限制。同时需要一个hashmap将val的值映射到Node *,用来找Node。

Union 根据rank合并。

#include <iostream>

struct Node{
    int val;
    Node *parent;
    int rank;
    Node(int v):val(v),parent(this),rank(0){}
};

class DisjointSet{
public:
    // Create a set with only one element
    void makeSet(int val){
        Node *node=new Node(val);
        m[val] = node;
    }
    
    // Combines two sets, unions by rank
    void unionSet(int val1, int val2){
        Node *node1=m[val1];
        Node *node2=m[val2];
        
        Node *root1=findSet(node1);
        Node *root2=findSet(node2);
        
        // they of part of the same set, do nothing
        if (root1==root2) return;
        
        // whoever's rank is higher becomes parent of other
        if (root1->rank > root2->rank)
            root2->parent = root1;
        else if (root1->rank < root2->rank)
            root1->parent = root2->parent;
        else{
            root1->rank += 1;
            root2->parent = root1;
        }
    }
    
    int findSet(int val){
        return findSet(m[val])->val;
    }
    
    // Find the representative recursively and does path compression as well
    Node *findSet(Node *node){
        Node *parent=node->parent;
        if (parent==node) return node;
        node->parent = findSet(node->parent);
        return node->parent;
    }
    
private:
    unordered_map<int,Node *> m; // node val -> Node *
};


int main(){
    DisjointSet a;
    for (int i=1;i<=7;++i) a.makeSet(i);
    a.unionSet(1,2);
    a.unionSet(2,3);
    cout << a.findSet(1) << endl;
    cout << a.findSet(3) << endl;
    cout << a.findSet(7) << endl;
    return 0;
}

 

应用中,可以简化Union,不用根据size,rank合并,直接把一个节点的root赋为另一个节点root的父亲即可。

 

转载于:https://www.cnblogs.com/hankunyan/p/9998626.html

<think>我们被要求解释DisjointSet(并查集)的定义和用途。根据之前的对话历史和提供的引用,我们可以总结如下:定义:并查集(DisjointSet)是一种用于处理不相交集合的数据结构,它主要支持两种操作:1.查找(Find):确定某个元素属于哪一个子集(或者说是确定某个元素的“根”)。2.合并(Union):将两个子集合并成一个集合。在并查集中,每个集合用一个代表元素(通常是集合的根节点)来标识。当我们需要合并两个集合时,只需将一个集合的根节点指向另一个集合的根节点。用途:并查集的主要用途是高效地管理元素的连通性。例如:-在图中检查两个节点是否连通,以及合并两个连通分量。-在Kruskal算法中用于判断边的两个顶点是否属于同一个连通分量以避免形成环。-在一些动态连通性问题中(如网络连接、社交网络的朋友圈等)。实现:根据引用[1][3][4],我们通常用一个数组(称为father或parent)来存储每个元素的父节点。初始时,每个元素自成一个集合,即father[i]=i。操作细节:1.初始化:每个元素都是自己的父节点(自己就是根)。2.查找(Find):通过不断向上查找直到找到根节点(根节点的特点是father[x]==x)。为了优化,我们使用路径压缩:在查找的过程中,将沿途上的节点的父节点都直接指向根,以减少后续查找的深度。3.合并(Union):先分别找到两个元素的根,如果根相同则已经在同一个集合,无需合并;否则,将一个根的父节点设为另一个根。为了平衡树的高度,我们还可以使用按秩合并(rank):总是将深度较小的树合并到深度较大的树上,这样树的高度不会增加。如果两棵树高度相同,合并后高度加一。根据引用[1]的描述,合并操作是将一个集合的根的父亲指向另一个集合的根。在引用[3]和[4]中,我们看到了具体的代码实现。我们接下来给出一个并查集的完整实现,包括路径压缩和按秩合并(虽然引用[3]和[4]的代码没有按秩合并,但引用[1]中提到了合并操作,我们在代码中增加按秩合并以优化)。同时,我们将用中文解释其定义和用途。此外,根据指令,我们需要在末尾生成相关问题。注意:用户现在想了解定义和用途,所以我们重点放在基础概念上,避免深入算法细节(如Kruskal算法已经讨论过,这里只需简单提及)。结构:1.定义并查集2.核心操作3.代码实现(参考多个引用,编写一个优化版本)4.并查集的用途5.相关问题</think> ### 并查集(Disjoint Set)定义与核心用途 #### 一、基本定义 并查集是一种**处理不相交集合合并与查询问题**的高效数据结构[^1],包含两个核心操作: 1. **Find(查询)**:确定元素所属的集合(通常返回集合代表元素) 2. **Union(合并)**:将两个不相交集合合并为一个新集合 ```mermaid graph TB subgraph 初始状态 A[0]-->A B[1]-->B C[2]-->C end subgraph 合并Union(0,1) A-->B B-->B C-->C end subgraph 合并Union(1,2) A-->B B-->B C-->B end ``` #### 二、核心操作原理 1. **存储结构** 使用`father[]`数组(又称`parent[]`)存储元素的父节点引用[^1][^3]: ```python # 初始化:每个元素独立成集合 father = [0,1,2,...,n-1] # father[i] = i ``` 2. **Find操作** - 功能:查找元素所在集合的根节点(代表元素) - 优化:**路径压缩**(Path Compression) ```python def find(x): if father[x] != x: father[x] = find(father[x]) # 递归压缩路径 return father[x] ``` 3. **Union操作** - 功能:合并两个元素所在集合 - 优化:**按秩合并**(Union by Rank) ```python rank = [0]*n # 存储树的高度 def union(x, y): rx, ry = find(x), find(y) if rx == ry: return # 已属同集合 # 矮树合并到高树上(避免树过高) if rank[rx] < rank[ry]: father[rx] = ry elif rank[rx] > rank[ry]: father[ry] = rx else: father[ry] = rx rank[rx] += 1 # 高度相同时树高+1 ``` #### 三、关键性质与性能 | 特性 | 说明 | |------|------| | **连通性检测** | 判断元素连通性:$find(a) = find(b)$ [^2] | | **时间复杂度** | 单次操作均摊$O(\alpha(n))$(阿克曼反函数)[^4] | | **空间复杂度** | $O(n)$(存储father和rank数组) | | **动态性** | 支持集合的实时合并与查询 | #### 四、主要应用场景 1. **图论算法** - Kruskal最小生成树算法中的环检测[^2] - 连通分量实时统计 2. **动态连通性问题** - 社交网络好友关系合并(如:合并好友圈) - 图像处理中的像素区域合并 3. **编译器设计** - 变量等价关系推断(如:并查集实现类型等价) 4. **棋盘类游戏** - 围棋/象棋的连通区域判定 #### 五、典型代码实现 ```python class DisjointSet: def __init__(self, n): self.father = list(range(n)) self.rank = [0] * n def find(self, x): # 路径压缩查询 if self.father[x] != x: self.father[x] = self.find(self.father[x]) return self.father[x] def union(self, x, y): # 按秩合并 rx, ry = self.find(x), self.find(y) if rx == ry: return False if self.rank[rx] < self.rank[ry]: self.father[rx] = ry elif self.rank[rx] > self.rank[ry]: self.father[ry] = rx else: self.father[ry] = rx self.rank[rx] += 1 return True ``` #### 六、性能优化意义 | 优化方法 | 未优化复杂度 | 优化后复杂度 | 原理 | |---------|------------|------------|------| | 普通并查集 | $O(\log n)$ | $O(\alpha(n))$ | 避免树退化成链 | | 路径压缩 | - | 查询扁平化 | 降低后续查询深度 | | 按秩合并 | - | 控制树高度 | 合并时平衡树结构 | > 其中$\alpha(n)$是阿克曼反函数,在$n ≤ 10^{100}$时$\alpha(n) ≤ 4$[^4] --- ### 相关问题 1. **路径压缩与按秩合并能否同时使用?它们如何相互影响时间复杂度?** 2. **如何用并查集检测图中是否存在环?时间复杂度是多少?** 3. **在动态图连通性问题中,并查集相比DFS/BFS有哪些优势?** 4. **并查集能否高效支持集合的分裂操作?为什么?** 5. **如何处理带权并查集问题(如食物链问题)?** [^1]: 并查集通过father数组组织树形结构维护集合关系 [^2]: 集合操作本质是通过操作树根实现的 [^3]: 路径压缩是保证查询高效的核心技术 [^4]: 按秩合并与路径压缩共同实现次线性复杂度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值