基于基本数据类型的并查集实现请参考QuickUnion。
自定义对象并查集的底层实现:链表+Map映射 [这里的链表表现为树]
逻辑上:自下而上的多棵树。
数组已经满足不了自定义对象并查集,可以定义内部类Node形成Node节点来封装每个自定义对象。这里提供树高rank属性,通过rank控制树相对平衡以提高并查集使用效率。
private static class Node<V>{
V value;
Node<V> parent = this; //初始化时自己的父节点就是自己
int rank = 1; //初始化时,树高为1
}
提供initSet方法初始化并查集:
public void initSet(V v) {
if(nodes.containsKey(v)) return;
nodes.put(v, new Node<>(v)); //一个v对应一个节点
}
实例化对象调用initSet方法后每个自定义对象自成一个集合:
定义私有findRoot方法 :从给定节点处不断上探,直至找到根节点,上探过程使用路径减半优化策略来提高效率。
private Node<V> findRoot(V v){
Node<V> node = nodes.get(v); //拿到v对应的Node节点
if(node == null) return null;
//从node开始上探,直至根节点
while(!Objects.equals(node.value, node.parent.value)) {
node = node.parent.parent; //使用了路径减半优化
}
return node;
}
定义find方法:查找自定义对象所属集合,封装自定义对象的节点的根节点即其所在集合。
public V find(V v) {
Node<V> node = findRoot(v); //拿到v所在的根节点
return node == null ? null : node.value;
}
定义union方法:对给定两个自定义对象,合并封装自定义对象的两个节点。先拿到两个节点的根节点,对根节点嫁接即可。
Node<V> rt1 = findRoot(v1);
Node<V> rt2 = findRoot(v2)
分三种情况:
(1)左边树高低于右边树高,此时直接将左边树的根节点嫁接到右边树的根节点上,树整体高度不变。
(2)左边树高高于右边树高,此时直接将右边树的根节点嫁接到左边树的根节点上,树整体高度不变。
(3)左边树高等于右边树高,默认将右边树的根节点嫁接到左边树的根节点,树整体高度加1。
三种情况代码实现:
if(rt1.rank < rt2.rank) { //以rt1为根节点的树高<以rt2为根节点的树高
rt1.parent = rt2; //rt1嫁接到rt2
}else if(rt1.rank > rt2.rank) { //以rt1为根节点的树高>以rt2为根节点的树高
rt2.parent = rt1; //rt2嫁接到rt1
}else { //以rt1为根节点的树高=以rt2为根节点的树高
rt2.parent = rt1; //默认rt2嫁接到rt1
rt1.rank += 1; //更新rt2的树高
}
定义isTogether方法:判断给定两个对象是否所属同一集合。封装自定义对象的节点的根节点相同即属同一集合,否则不属不同集合。
public boolean isTogether(V v1, V v2) {
return Objects.equals(find(v1), find(v2));
}
自此,通用并查集完成,基本数据类型,自定义对象均可使用。
提供完整代码块供参考:
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 底层实现 : 链表+Map映射 [这里的链表表现为简单树] 逻辑上:自下而上的多棵树
* @author Asus
*
* @param <V> 传入的自定义对象
*/
public class GenericUnionFind<V> {
private Map<V, Node<V>> nodes = new HashMap<>();
//初始化并查集
public void initSet(V v) {
if(nodes.containsKey(v)) return;
nodes.put(v, new Node<>(v)); //一个v对应一个节点
}
//返回v的根节点
public V find(V v) {
Node<V> node = findRoot(v); //拿到v所在的根节点
return node == null ? null : node.value;
}
//查找根节点
private Node<V> findRoot(V v){
Node<V> node = nodes.get(v); //拿到v对应的Node节点
if(node == null) return null;
//从node开始上探,直至根节点
while(!Objects.equals(node.value, node.parent.value)) {
node = node.parent.parent; //使用了路径减半优化
}
return node;
}
public void union(V v1, V v2) {
//拿到v1,v2的根节点
Node<V> rt1 = findRoot(v1);
Node<V> rt2 = findRoot(v2);
if(rt1 == null || rt2 == null) return;
if(rt1.equals(rt2)) return; //在同一集合则不需要合并
if(rt1.rank < rt2.rank) { //以rt1为根节点的树高<以rt2为根节点的树高
rt1.parent = rt2; //rt1嫁接到rt2
}else if(rt1.rank > rt2.rank) { //以rt1为根节点的树高>以rt2为根节点的树高
rt2.parent = rt1; //rt2嫁接到rt1
}else { //以rt1为根节点的树高=以rt2为根节点的树高
rt2.parent = rt1; //默认rt2嫁接到rt1
rt1.rank += 1; //更新rt2的树高
}
}
//判断v1,v2是否所属同一集合
public boolean isTogether(V v1, V v2) {
return Objects.equals(find(v1), find(v2));
}
//定义节点
private static class Node<V>{
V value;
int rank = 1; //默认以自己为根节点的树高为1
Node<V> parent = this; //默认自己的父节点为自己
Node(V v){
this.value = v;
}
}
}
完结…