通用并查集 - 基于QuickUnion实现(Java语言)

基于基本数据类型的并查集实现请参考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;
		}
	}
}

完结…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值