并查集总结


维基百科解释
在计算机科学中,并查集(英文:Disjoint-set data structure,直译为不交集数据结构)是一种数据结构,用于处理一些不交集(Disjoint sets,一系列没有重复元素的集合)的合并及查询问题。并查集支持如下操作:

  1. 查询:查询某个元素属于哪个集合,通常是返回集合内的一个“代表元素”。这个操作是为了判断两个元素是否在同一个集合之中。
  2. 合并:将两个集合合并为一个。
  3. 添加:添加一个新集合,其中有一个新元素。添加操作不如查询和合并操作重要,常常被忽略。

由于支持查询和合并这两种操作,并查集在英文中也被称为联合-查找数据结构(Union-find data structure)或者合并-查找集合(Merge-find set)。

在这里插入图片描述
有两种常见的思路:

  1. Quick Find
查找(Find)的时间复杂度O(1)
合并(Union)的时间复杂度O(n) 
  1. Quick Union
查找(Find)的时间复杂度O(a(n)) ,a(n)<5
合并(Union)的时间复杂度O(a(n)) ,a(n)<5

如何处理数据

  1. 假设并查集处理的数据都是整数,那么可以用整型数组来存储数据
    在这里插入图片描述
  2. 因此,并查集是可以用数组实现的树形结构(二叉堆、优先级队列也是可以用数组实现的树形结构)

接口定义

int find(int v)
//默认是将v1合并到v2上去
void union(int v1,int v2)
boolean isSame(int v1,int v2)

初始化

	private int[] parents;
    public UnionFind(int capacity){
        if(capacity<0){
            throw new IllegalArgumentException("capacity must >=1");
        }
        parents = new int[capacity];
        for(int i =0;i<parents.length;i++){
            parents[i]=i;
        }
    }

Quick Find

在这里插入图片描述在这里插入图片描述

public class UnionFind {
    private int[] parents;
    public UnionFind(int capacity){
        if(capacity<0){
            throw new IllegalArgumentException("capacity must >=1");
        }
        parents = new int[capacity];
        for(int i =0;i<parents.length;i++){
            parents[i]=i;
        }
    }
    public int find(int v){
        //具体实现上需要判断是否越界
        return parents[v];
    }
    //默认是将v1合并到v2上去
    public void union(int v1,int v2){
        int p1= find(v1);
        int p2 =find(v2);
        if(p1 == p2) return ;
        for(int i =0;i<parents.length;i++){
            if(parents[i]==p1)
                parents[i]=p2;
        }
    }
    public boolean isSame(int v1,int v2){
        return find(v1)==find(v2);
    }
}

Quick Union

Quick Union 的union(v1,v2):是让v1的根节点指向v2的根节点
所以需要对find和union重写

public int find(int v){
        //具体实现上需要判断是否越界
        while(v!=parents[v]){
            v = parents[v];
        }
        return v;
    }

    //默认是将v1合并到v2上去
    public void union(int v1,int v2){
        int p1= find(v1);
        int p2 =find(v2);
        if(p1 == p2) return ;
        parents[p1] = p2;
    }

优化

在union的时候,可能会出现树不平衡的情况,甚至退化成链表
在这里插入图片描述


有两种常见的优化方案:

  1. 基于size的优化:元素少的树嫁接到元素多的数
  2. 基于rank的优化:矮的树嫁接到高的树

基于size的优化:需要再设置一个sizes变量

元素少的树嫁接到元素多的数

public class UnionFind {
    private int[] parents;
    private int[] sizes;
    public UnionFind(int capacity){
        if(capacity<0){
            throw new IllegalArgumentException("capacity must >=1");
        }
        parents = new int[capacity];
        sizes = new int[capacity];
        for(int i =0;i<parents.length;i++){
            parents[i]=i;
            sizes[i]=1;
        }
    }
    public int find(int v){
        //具体实现上需要判断是否越界
        while(v!=parents[v]){
            v = parents[v];
        }
        return v;
    }
    //默认是将v1合并到v2上去
    public void union(int v1,int v2){
        int p1= find(v1);
        int p2 =find(v2);
        if(p1 == p2) return ;
        if(sizes[p1]<sizes[p2]){
            parents[p1] =p2;
            sizes[p2]+=sizes[p1];
        } else{
            parents[p2] = p1;
           sizes[p1]+=sizes[p2];
        }
    }
    public boolean isSame(int v1,int v2){
        return find(v1)==find(v2);
    }
}

基于rank的优化

矮的树嫁接到高的树

public class UnionFind {
    private int[] parents;
    private int[] ranks;
    public UnionFind(int capacity){
        if(capacity<0){
            throw new IllegalArgumentException("capacity must >=1");
        }
        parents = new int[capacity];
        ranks = new int[capacity];
        for(int i =0;i<parents.length;i++){
            parents[i]=i;
            ranks[i]=1;
        }
    }
    public int find(int v){
        //具体实现上需要判断是否越界
        while(v!=parents[v]){
            v = parents[v];
        }
        return v;
    }
    //默认是将v1合并到v2上去
    public void union(int v1,int v2){
        int p1= find(v1);
        int p2 =find(v2);
        if(p1 == p2) return ;
        if(ranks[p1]<ranks[p2]){
            parents[p1] =p2;
        } else if(ranks[p2]<ranks[p1]){
            parents[p2] = p1;
        } else{
            parents[p1]=p2;
            ranks[p2]++;
        }
    }
    public boolean isSame(int v1,int v2){
        return find(v1)==find(v2);
    }
}

路径压缩(Path Compression)

虽然有了基于rank的优化,树会相对平衡一点
但是随着union次数的增多,树的高度依然会越来越高,导致find变慢,尤其是底层节点
路径压缩指的是 在find时使路径上的所有节点都指向根节点,从而降低树的高度
public int find(int v){
        //具体实现上需要判断是否越界
        while(parents[v]!=v){
            parents[v] = find(parents[v]) ;
        }
        return parents[v];
    }

路径分裂(Path Spliting)


使路径上的每个节点都指向其祖父节点(父亲的父亲)


public int find(int v){
        //具体实现上需要判断是否越界
        while(v !=parents[v]){
            int parent = parents[v];
            parents[v] = parents[parent] ;
            v = parent;
        }
        return v;
    }

路径减半(Path Halving)


使路径上每隔一个节点就指向其祖父节点


public int find(int v) { 
		while (v != parents[v]) {
			parents[v] = parents[parents[v]];
			v = parents[v];
		}
		return v;
	}

总结

之前使用的都是整型类型,如果时其它的自定义类型也想使用并查集怎么办呢

  1. 通过一些方法将自定义类型转为整型后使用并查集(比如哈希值)
  2. 使用链表+映射(Map)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值