并查集详解

并查集

参考:【算法与数据结构】—— 并查集-CSDN博客

1. 概论

定义:

并查集是一种树形的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。

主要构成:

并查集主要由一个整型数组pre[] 和两个函数 find()join()构成

pre[] 记录每个点的前驱节点。

find(x) 用于查找指定节点 x 属于哪个集合,返回该集合的根节点(元节点)

join(x, y) 用于合并两个节点 xy

适用:

  • 求连通分支数(如果一个图中所有点都存在可达关系(直接或间接相连),则此图的连通分支数为1;如果此图有两大子图各自全部可达,则此图的连通分支数为2……)
  • 求图中任意两个节点是否连通(存在路径)。

2. 并查集的现实意义:

对于若干个具有层级关系的连通图(树),快速查找两个节点是否连通,不关心他们具体是如果连通的,

内部结构是怎样的,甚至根节点是哪个,都不重要。所以并查集在初始化时,教主可以随意选择(就不必再搞什么武林大会了),只要能分清敌友关系就行。

在这里插入图片描述

3. find() 函数实现

定义

find() 函数要实现找到节点x所属集合的根节点,可以结合pre[] 数组找,通过pre[]数组一直找x的上级,直到pre[x] == x

public int find(int x){
    while(pre[x] != x){
        x = pre[x];
    }
    return x;
}

在这里插入图片描述

**优化:**路径压缩优化

上述再找根节点的时候,需要一级一级向上找,不过这个步骤是不必要的,我们只关心x的根节点是谁,不关心x 经过了多少节点才找到根节点,因此把x 的上级直接设成根节点最好,可以提升效率,这被称为路径压缩。

public int find(int x){
    if(pre[x] == x) return x;
    reutrn pre[x] = find(pre[x]); // 找到根节点,然后将x的前驱节点设成根节点。
}

在这里插入图片描述

4. join() 函数实现

定义:

join(x, y) 函数是将x, y 两个节点建立联系,也就是将xy 所属的连通图连起来形成一个连通图。如果x,y 属于一个图,则不用操作,如果不属于,则找到两者的根节点,将一个根节点的父节点设置成另一个根节点即可。

public void join(int x, int y){
    int px = find(x), py = find(y);
    if(px == py)return;
    pre[px] = py;//让py 作为 px 的前驱。
}

优化: 加权标记优化

上述操作是随机选择,我们直到两个树的高度可能不同,如果将高度高的树接在高度低的树上则会增加总体高度,影响效率。因此给每个节点引入一个权值rank[i],该权值记录节点的高度,在合并的时候通过比较节点高度,将高度低的接在高度高的树上。

public boolean join(int x, int y){
        int px = find(x), py = find(y);
    	if(px == py)return false;
        if(rank[px] > rank[py])
            pre[py] = px;
        else{
            // 如果两棵树高度相同,将 py 当最终根节点时py的高度会 + 1
            if(rank[x] == rank[y]){
                rank[y] ++;
            }
            pre[px] = py;
        }
        return true;
	}

在这里插入图片描述

5. 查看两个节点是否连通

public boolean connect(int x, int y) {
        return find(x) == find(y);
 }

6. 查看连通集合的个数

使用路径压缩的并查集,如果pre[x] = x 就说明存在一个集合。

public int getConnectNum(){
    int res = 0;
    for(int i = 0; i < pre.length; i ++){
		if(pre[i] = i)res ++;
    }
    return res;
}

7. 总体代码实现

public calss UnionSet{
    int[] pre;	// 前驱数组
    int[] rank;	// 权值数组
    public Unionset(int n){
        pre = new int[n];
        rank = new int[n];
        this.init(n);
    }
    // 初始化
    private void init(int n){
        for(int i = 0; i < n; i ++){
            pre[i] = i;
            ran[i] = 1;
        }
    }
    
    public int find(int x){
        if(pre[x] == x) return x;		//递归出口:x的上级为 x本身,即 x为根结点 
    	return pre[x] = find(pre[x]);   //此代码相当于先找到根结点 rootx,然后 pre[x]=rootx 
    }
    
    public boolean join(int x, int y){
        int px = find(x), py = find(y);
    	if(px == py)return false;
        if(rank[px] > rank[py])
            pre[py] = px;
        else{
            // 如果两棵树高度相同,将 py 当最终根节点时py的高度会 + 1
            if(rank[x] == rank[y]){
                rank[y] ++;
            }
            pre[px] = py;
        }
        return true;
	}
    
    public boolean connect(int x, int y) {
        return find(x) == find(y);
 	}
    
    public int getConnectNum(){
        int res = 0;
        for(int i = 0; i < pre.length; i ++){
            if(pre[i] = i)res ++;
        }
        return res;
	}
    
}
  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值