并查集

2018/04/01 Algorithms 共 4290 字,约 13 分钟

定义

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。就是元素可以为集合的集合

实现

可以用pre[1000]数组来存储这个并查集

初始化: 每个节点初始状态都是一个自身的集合,即pre[i]=i;

void InitSet(int n){
    for(int i=0;i<n;i++){
        pre[i]=i;
    }
}

查找根节点及路径压缩

查找根节点就是返回集合的代表元素所在,路径压缩就是把所有非根节点的父节点修改为根节点,这样,就变成了仅有根节点和叶子节点的树,仅有两层

int  pre[1000];  
int Find(int x)  
{  
    //以下实现查找根节点
    int r=x;  
    while(r!=pre[r])  
        r=pre[r];  
    
    //以下代码用来实现路径压缩
    int i=x,j;  
    while(pre[i]!=r)  
    {  
        j=pre[i];  
        pre[i]=r;  
        i=j;  
    }  
    return r;  
}  

也可以下面的,这个包含了查找和路径压缩,fa就是pre

int Find(int x) {  
    return fa[x] == x ? x : fa[x] = Find(fa[x]) ;  
} 

集合合并

查找两个集合的根节点并合二为一

void mix(int x,int y)  
{  
    int fx=Find(x),fy=Find(y);  
    if(fx!=fy)  
    {  
        pre[fy]=fx;  
    }  
}   

模板

python3

class UnionFind:
    def __init__(self):
        self.parent = {}

    def union(self, a: int, b: int):
        ra = self.find(a)
        rb = self.find(b)
        self.parent[ra] = rb

    def find(self, a: int) -> int:
        if a not in self.parent:
            self.parent[a] = a
        if self.parent[a] == a:
            return a
        ra = self.find(self.parent[a])
        self.parent[a] = ra
        return ra

    def is_connected(self, a, b):
        return self.find(a) == self.find(b)

python3 带每个集合数量

class UnionFind:
    def __init__(self):
        self.parent = {}
        self.size = {}

    def union(self, a: int, b: int):
        ra = self.find(a)
        rb = self.find(b)
        if ra == rb: return
        self.parent[ra] = rb
        self.size[rb] += self.size[ra]

    def find(self, a: int) -> int:
        if a not in self.parent:
            self.parent[a] = a
            self.size[a] = 1
        if self.parent[a] == a:
            return a
        ra = self.find(self.parent[a])
        self.parent[a] = ra
        return ra

    def is_connected(self, a, b):
        return self.find(a) == self.find(b)

相关题目

java实现


import java.io.BufferedInputStream;
import java.util.ArrayDeque;
import java.util.Scanner;

public class Solution {
    static final int maxn=100000;
    static int[] f=new int[maxn];
    static int find(int x){
        return x==f[x]?x:(f[x]=find(f[x]));
    }
    static void union(int x,int y){
        int fa=find(x);
        int fb=find(y);
        if(fa!=fb){
            f[fa]=fb;
        }
    }
    static void init(){
        for(int i=0;i<f.length;i++){
            f[i]=i;
        }
    }
    public static void main(String[] args){
        Scanner in=new Scanner(new BufferedInputStream(System.in));
        init();
        while (in.hasNext()){
            int a=in.nextInt();
            int b=in.nextInt();
            union(a,b);
            System.out.println(find(a));
        }
//        System.out.println(f[3]);
//        System.out.println(find(3));

    }
}

应用

  1. 求连通图的数量 用一个新的数组t[1000],并fill(t,t+1000,0); 然后遍历所有节点,t(Find(x))=1,数组t中为1的数量就是连通子图的数量

  2. 求修路问题? 就是连通图数量减一呀

  3. Kruskal最小生成树用的好像就是这个呀!!

例题

例题链接:求修道路数量

#include<iostream>  
using namespace std;  
  
int  pre[1050];  
bool t[1050];               //t 用于标记独立块的根结点  
  
int Find(int x)  
{  
    int r=x;  
    while(r!=pre[r])  
        r=pre[r];  
      
    int i=x,j;  
    while(pre[i]!=r)  
    {  
        j=pre[i];  
        pre[i]=r;  
        i=j;  
    }  
    return r;  
}  
  
void mix(int x,int y)  
{  
    int fx=Find(x),fy=Find(y);  
    if(fx!=fy)  
    {  
        pre[fy]=fx;  
    }  
}   
  
int main()  
{  
    int N,M,a,b,i,j,ans;  
    while(scanf("%d%d",&N,&M)&&N)  
    {  
        for(i=1;i<=N;i++)          //初始化   
            pre[i]=i;  
          
        for(i=1;i<=M;i++)          //吸收并整理数据   
        {  
            scanf("%d%d",&a,&b);  
            mix(a,b);  
        }  
          
          
        memset(t,0,sizeof(t));  
        for(i=1;i<=N;i++)          //标记根结点  
        {  
            t[Find(i)]=1;  
        }  
        for(ans=0,i=1;i<=N;i++)  
            if(t[i])  
                ans++;  
                  
        printf("%d\n",ans-1);  
          
    }  
    return 0;  
}//dellaserss  

冗余连接

684. Redundant Connection

题目描述:有一系列的边连成的图,找出一条边,移除它之后该图能够成为一棵树。

public int[] findRedundantConnection(int[][] edges) {
    int N = edges.length;
    UF uf = new UF(N);
    for (int[] e : edges) {
        int u = e[0], v = e[1];
        if (uf.connect(u, v)) {
            return e;
        }
        uf.union(u, v);
    }
    return new int[]{-1, -1};
}

private class UF {
    private int[] id;

    UF(int N) {
        id = new int[N + 1];
        for (int i = 0; i < id.length; i++) {
            id[i] = i;
        }
    }

    void union(int u, int v) {
        int uID = find(u);
        int vID = find(v);
        if (uID == vID) {
            return;
        }
        for (int i = 0; i < id.length; i++) {
            if (id[i] == uID) {
                id[i] = vID;
            }
        }
    }

    int find(int p) {
        return id[p];
    }

    boolean connect(int u, int v) {
        return find(u) == find(v);
    }
}

加权并查集

参考

必看:https://leetcode-cn.com/problems/evaluate-division/solution/399-chu-fa-qiu-zhi-nan-du-zhong-deng-286-w45d/

区别: 除了维护 parent 这样的节点指向关系,我们还需要维护节点的权重,一个简单的想法是使用另外一个哈希表 weight 存储节点的权重关系。比如 weight[a] = 1 表示 a 到其父节点的权重是 1。

除法的并查集

class Solution:
    root = {}

    def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
        self.root = {}
        for i in range(len(equations)):
            ei = equations[i]
            vi = values[i]
            self.union(ei[0], ei[1], vi)
        print(self.root)
        res = []
        for iq in queries:
            a, b = iq[0], iq[1]
            if a in self.root and b in self.root:
                a_ra, ra = self.find(a)
                b_rb, rb = self.find(b)
                if ra != rb:
                    res.append(-1.0)
                else:
                    res.append(a_ra / b_rb)
            else:
                res.append(-1.0)
        return res

    def union(self, a, b, a_b):
        if a not in self.root:
            self.root[a] = [1.0, a]
        if b not in self.root:
            self.root[b] = [1.0, b]
        a_ra, ra = self.find(a)
        b_rb, rb = self.find(b)
        self.root[ra] = [b_rb * a_b / a_ra, rb]

    def find(self, i):
        tmp = i
        assert i in self.root  # i invalid
        path, rate, base = [], [], 1.0
        while self.root[i][1] != i:
            path.append(i)
            rate.append(self.root[i][0])
            base *= self.root[i][0]
            i = self.root[i][1]
        ra = i
        for i in range(len(path)):
            ip = path[i]
            self.root[ip][1] = ra
            self.root[ip][0] = base
            base = base / rate[i]
        return self.root[tmp]

Search

    Table of Contents