并查集:在进行判断两个数据之间是不是有连接的操作中。如果我们单独用数组进行数据的存储,把有连接的数据的ID设置成相同的数字,那么这种数据结构进行查找操作的时候,时间复杂度是O(1),但是进行合并的时候,所进行的时间复杂度是O(n)。对于数据量较大的情况,O(n)将会是个非常慢的操作。所以这个时候需要并查集这种数据结构。这种数据结构更像是森林,每棵树上的数据是有连接的,不同树之间的数据是没有连接的。如果想合并,只需要将两棵树的根节点进行合并。所以这就导致了并查集的树和别的树不一样。
并查集的树的结构是子节点指向父节点,父节点的值指向自己。底层实现仍然是数组,数组的index是所要查找的节点,数组的值代表的是这个节点上一层的节点。想要判断两个节点是否有连接,只需要判断两个节点的根节点是不是同一个值。这样达到的时间复杂度在查找和合并上都是h(h代表的是树的深度)。所以肯定是树的深度越低越好,这就要进行优化,可以定义一个数组记录每个根节点底下树的深度,当进行合并的时候,把根节点树的深度低的连接向根节点树深度高的节点。除了这个优化之外,还可以进行路径的压缩,就是当进行查询根节点的过程中,把要相应的节点跳过父节点,而指向父亲的父亲节点,然后一直循环到根节点。这样可以有效降低树的深度。所以那个记录的也不能成为树的深度,可以理解成树的等级,深度越高等级越高,每次都把高等级的树连接到低等级的树的根节点上。
//要实现的接口
public interface UF {
public int getSize();
public void union(int p,int q);
public boolean isConnection(int p,int q);
}
//实现接口的类
public class QuickUnion implements UF{
//记录每个数值所属的集合
private int[] ID;
//记录每个根节点下的子节点的个数
private int[] rank;
public QuickUnion(int size) {
ID = new int[size];
rank = new int[size];
for(int i = 0;i<size;i++) {
ID[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize() {
return ID.length;
}
//找到节点的父亲节点并在查找的过程中进行路径的压缩
public int find(int p) {
while(p != ID[p]) {
ID[p] = ID[ID[p]];
p = ID[p];
}
return p;
}
//判断两个节点是否有链接
@Override
public boolean isConnection(int p, int q) {
return find(p) == find(q);
}
//合并两个节点
@Override
public void union(int p,int q) {
int pID = find(p);
int qID = find(q);
if(pID == qID) {
return ;
}
if(rank[pID]<rank[qID]) {
ID[pID] = qID;
}
else if(rank[pID]>rank[qID]){
ID[qID] = pID;
}
else {
ID[qID] = pID;
rank[qID] +=1;
}
}
}