一、啥是并查集?
它是一种方法,在某种规则的限制下,把N个单独的有相同性质节点划分到一个集合里。
二、用来解决什么问题?
能用 分拨方式 解决的问题都可以用这个思想解决。
分拨顾名思义,谁和谁一伙,谁和谁好了,这个圈里有多少人,那个圈里有多少人的。为了干成一件事,每个圈里都要贡献一份力啥的。
三、讲解思想
上面这张图里一共有10个节点,每个节点的连通性如图所示。现在我们对连通性描述如下:
0-1是直接相连,0-3是间接相连、2-3也是间接相连,0-4不相连。
我们现在使用二维数组(arr)来表示上图里的节点与连通性,arr[i][j] 的取值有2个,分别是0、1。现在对取值描述如下:
1:i节点与j节点直接相连。
0:i节点与j节点非直接相连。
根据上图我们知道二维数组(arr)的值是下面这样:
let arr = [
// 0 1 2 3 4 5 6 7 8 9 colIndex
[1, 1, 1, 0, 0, 0, 0, 0, 0, 0], // 0 rowIndex
[1, 1, 0, 1, 0, 0, 0, 0, 0, 0], // 1
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0], // 2
[0, 1, 0, 1, 0, 0, 0, 0, 0, 0], // 3
[0, 0, 0, 0, 1, 0, 0, 0, 1, 0], // 4
[0, 0, 0, 0, 0, 1, 1, 1, 0, 0], // 5
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0], // 6
[0, 0, 0, 0, 0, 1, 0, 1, 0, 0], // 7
[0, 0, 0, 0, 1, 0, 0, 0, 1, 0], // 8
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1], // 9
]
目前为止,我们可以通过二维数组来表示上图的节点与联通性,那么问题来了,根据这个二维数组,我们如何得知有几个集合呢?
起初,我们10个节点各自为战,所以此时总共有10个集合。
我们声明一个root数组,用于存储这10个节点与它的连通性。
let arr = [...];
let root = [];
for (let index = 0; index < arr.length; index++){
root.push(index);
}
root数组代表的含义:索引代表我们的节点,索引对应的值代表该节点的最祖先元素。
所以此时,再经过一系列的操作处理之后,我们的root数组的数据应该是这样的:
// root索引:0 1 2 3 4 5 6 7 8 9
let root = [0, 0, 0, 0, 4, 5, 5, 5, 4, 9];
正好也与我们的图里的节点对应上了:
好了,现在我们来看看 一系列操作 是怎么操作数据。
-
首先,遍历二维数组arr,如果arr[i][j] === 1,那么说明i与j是相连的,那么此时root[y]的值就应该更改为i的祖先元素。这个过程我们叫做“并操作(union函数)”。
-
其次,我们如何找 i 的祖先元素(这个过程叫做“查操作(find函数)”)?。
我们以节点3为例,来演示下在
节点3上进行的操作。
1、首先arr[1][3] === 1,意味着节点3与节点1是相连的,所以roor[3] = find(1)。
2、find函数是用来干啥的?上面已经说的很清楚了,root里存储的值都是祖先元素,那么find函数就是查找相应元素的祖先元素。
3、find(1) 就是查找节点1的祖先元素,在arr没有被遍历结束之前,我们还不知道节点1是否有祖先元素,以及祖先元素是否被更改。
4、在第3步我们可知,在遍历过程中,节点的祖先元素是可以被更改的(或者说图与图之间是可以相连的),所以在我们进行并操作的时候,需要遍历root数组,要把那些被牵连的节点的对应值都更改掉。因为并操作的对象可以是根与根之间。
3.1、并操作
// 并操作
union(x, y){ // 将x、y节点联通
let rootX = find(x);
let rootY = find(y);
if (rootX !== rootY){
for (let index = 0; index < root.length; index++){
if (root[index] === rootY){
root[index] = rootX;
}
}
}
}
3.2、查操作
let arr = [...];
let root = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function find(suoyin){
return root[suoyin];
}
3.3、完整打通
let arr = [...];
let root = new Array(arr.length).map( (item, index) => index );
for (let rowIndex = 0; rowIndex < arr.length; rowIndex++){
for (let colIndex = 0; colIndex < arr.length; colIndex++){
if (arr[rowIndex][colIndex] === 1){ // 进行并操作
if (rowIndex <= colIndex){ // 我们并操作的原则是大节点向小节点靠拢,如果不这么做的话会造成循环重复。
union(rowIndex, colIndex);
} else {
union(colIndex, rowIndex);
}
}
}
}
console.log('root:', root);
此时root里的元素就都是被分拨了,数组里有多少个不重复的元素就有多少个集合。
四、最后
好啦,基本的并查集思想到这里就结束啦,如果在上述过程中出现了什么错误,欢迎指正,那么我们下回再见啦。