并查集的一个简单实现
引言:并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题,例如kruskal算法.
根据其原理我们简单实现一个.
并查集主要有两个操作:合并,查找
我们可以回忆下链表,假设某些元素都在同一个链表,我们则认为这些元素是同一个集合
如a:{1,2,3},集合中元素1 和 2查找的结果是同一个集合为true,然后我们关联{1,4},那么元素3和4的查找结果也是true
实现思路:
借用链表的思路,我们可以在初始化的时候,将所有不同元素都设置为root根节点,然后合并的时候,将一个root的parent设置为另一个root
这样我们可以根据两个元素的root是不是同一个,来判断两个元素的关联性
public class UnionFind<V> { // 每个Node的parent映射, root的parent是自己 private Map<Node<V>, Node<V>> parentMap; // v 和 Node 的映射 private Map<V, Node<V>> map; // root 的子节点的数量,只有root才有 private Map<Node<V>, Integer> rootSize; /** * @param list root的集合 */ public UnionFind(List<V> list) { parentMap = new HashMap<>(list.size()); map = new HashMap<>(list.size()); rootSize = new HashMap<>(list.size()); for (V v : list) { Node<V> node = new Node<>(v); map.put(v, node); parentMap.put(node, node); rootSize.put(node, 1); } } /** * 判断两个数据源是否是同一个集合 */ public boolean isSameSet(V a, V b) { return findRoot(a) == findRoot(b); } private Node<V> findRoot(V a) { Node<V> aNode = map.get(a); Deque<Node<V>> st = new ArrayDeque<>(); while (aNode != parentMap.get(aNode)) { st.push(aNode); aNode = parentMap.get(aNode); } // 优化,将当前所有的的子节点的parent都直接设置为root while (!st.isEmpty()) { Node<V> node = st.pop(); parentMap.put(node, aNode); } return aNode; } /* * 关联两个数据源 * */ public void union(V a, V b) { Node<V> aRoot = findRoot(a); Node<V> bRoot = findRoot(b); // 同一个集合直接返回 if (aRoot == bRoot) { return; } // 将小集合的root指向大节点的root if (rootSize.get(aRoot) >= rootSize.get(bRoot)) { // 将bRoot的parent设置为aRoot; parentMap.put(bRoot, aRoot); // 更新rootSize rootSize.put(aRoot, rootSize.get(bRoot) + rootSize.get(aRoot)); // 移除bRoot,因为此时已不再是根节点 rootSize.remove(bRoot); } else { parentMap.put(aRoot, bRoot); rootSize.put(bRoot, rootSize.get(bRoot) + rootSize.get(aRoot)); rootSize.remove(aRoot); } } static class Node<V> { V value; public Node(V value) { this.value = value; } } }
写一个简单的测试:
public static void main(String[] args) { List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9); UnionFind<Integer> uf = new UnionFind<>(list); System.out.println(uf.isSameSet(1,2));// false uf.union(1,2); uf.union(1,3); System.out.println(uf.isSameSet(2,3));// true }
感兴趣的可以取思考下操作的时间复杂度,注意上面的findRoot方法的优化操作