数据结构与算法之并查集基础知识

一个开源的个人学习计算机科学知识成长记录(前后端,数据结构与算法)

并查集

并查集主要用于解决元素分组问题,它支持对不相交的几何操作,主要是以下两种操作:

合并(Union) 合并两个不相交的集合

**查询(Find)**查询两个元素是否在同一个集合中

图结构

初始化

假如有编号为1, 2, 3, …, n的n个元素,我们用一个数组fa[]来存储每个元素的父节点(因为每个元素有且只有一个父节点,所以这是可行的)。一开始,我们先将它们的父节点设为自己。

const init = function(n){
    const fa = [];
    for(let i=1;i<=n;i++){
        fa[i] = i;
    }
}

查询

用递归的写法实现对代表元素的查询:一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。

const find = funtion(fa,x){
    if(fa[x] == x){
        return x;
    }else{
        return  find(fa[x]);
    }
}

合并

合并操作先找到两个集合的代表元素,然后将前者的父节点设为后者即可。当然也可以将后者的父节点设为前者,这里暂时不重要。本文末尾会给出一个更合理的比较方法。

const union = fucntion(i,j){
    fa[find[i]] = find[j]
}

路径压缩

使用路径压缩的方法。既然我们只关心一个元素对应的根节点,那我们希望每个元素到根节点的路径尽可能短,最好只需要一步

在查询的过程中,把沿途的每个节点的父节点都设为根节点即可。下一次再查询时,直接返回父节点即可

const find = function(fa,x){
    if(x == fa(x)){
        return x;
    }else{
        fa[x]=find([fa[x]]); // 设父节点为根节点
        return fa[x]; // 返回父节点
    }
}

// 简化写法

const find = function(fa,x){
    return x == fa[x]?x:(fa[x]=find(fa[x])); // 记得加括号
}

按秩合并

我们应该把简单的树往复杂的树上合并,而不是相反。因为这样合并后,到根节点距离变长的节点个数比较少

// 初始化
const init = function(n){
    const fa = [];
    const rank = [];
    for(let i=1;i<=n;i++){
        fa[i] = i;
        rank[i] =1;
    }
}
// 合并
const merge = function(fa,rank,i,j){
    const x = find(fa,i); //先找到两个根节点
    const y = find(fa,j);
    if(rank[x]<=rank[y]){
        fa[x]=y;
    }else{
        fa[y]=x;
    }
    if(rank[x]==rank[y]&&x!=y){
        rank[y]++; //如果深度相同且根节点不同,则新的根节点的深度+1
    }
}

P1551 亲戚问题

题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
输入格式
第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
输出格式
P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。

const readline = require('readline');  
const rl = readline.createInterface({  
    input: process.stdin,  
    output: process.stdout  
});

rl.on('close', function() {  
    console.log('程序结束');  
    process.exit(0);  
}); 

const readSingle = function(){
        const inputAr = [];
        rl.on('line', function (input) {  
            inputArr = input.split(" ");  
            rl.close();  
       }); 
       return inputArr;
}

const sibling =function(n,m,p,x,y){
    const fa = [];
    const rank = [];
    init(fa,rank,n);
    
    for(let i=0;i<m;++i){
        // 读取两个参数
        const read = readSingle();
        const  left = read[0];
        const right = read[1];
        merge(fa,rank,left,right);
    }
    
    for(let i=0;i<p:++i){
        // 读取两个参数
        const read = readSingle();
        const  left = read[0];
        const right = read[1];
        console.log(find(fa,left)==find(fa,right)?"Yes":"No")
    }
}
/**
* init
*/
const init = function(fa,rank,n){
    for(let i=0;i<=n;i++){
        fa[i] = i;
        rank[i] = 1;
    }
}
/**
*
*/
const  find = function(fa,x){
    return x == fa[x]?x:(fa[x]=find(fa[x]));
}
/*
* 将秩小的挂载到秩大的数值上
*/
const merge = function(fa,rank,i,j){
    const x = find(fa,i);
    const y = find(fa,j);
    if(rank[x]<=rank[y]){
        fa[x] = y;
    }else{
        fa[y] = x;
    }
    if(rank[x]==rank[y]&&x!=y){
        rank[y]++;
    }
}

总结

遇到并查集的时候,要初始化并查集,建立合并和查找的三个函数

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值