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

并查集

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

合并(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]++;
}
}

总结

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

参考