package com.datastructures; import java.io.Serializable; import java.util.*; /** * An implementation of disjoint-set, represented by rooted trees. *

* Actually, the instance of {@link DisjointSet} is a disjoint-set forests. * *

* Disjoint-set operations: *

* 1. quickly unites two sets into a new set, requiring O(1) time. *

* 2. quickly query two elements whether contained in the same set, requiring about O(1) time. * */ public class DisjointSet implements Serializable { private static final long serialVersionUID = 3134700471905625636L; private static final String elementKey = "element"; private final Map> nodeMap = new HashMap<>(); /** * Add an element to the disjoint-set forests as a set. */ public void makeSet(T element) { checkNotNull(element, elementKey); nodeMap.putIfAbsent(element, new Node<>()); } /** * Unites the set that contains left and the set that contains right * into a new set with the union-by-rank heuristic. *

* Rank is an upper bound on the height of node. */ public void union(T left, T right) { checkNotNull(left, elementKey); checkNotNull(right, elementKey); Node leftNode = nodeMap.get(left), rightNode = nodeMap.get(right); if (leftNode == null) { throw new NoSuchElementException(left.toString()); } if (rightNode == null) { throw new NoSuchElementException(right.toString()); } Node leftSet = findSet(leftNode), rightSet = findSet(rightNode); if (leftSet == rightSet) { return; } if (leftSet.rank < rightSet.rank) { leftSet.parent = rightSet; } else { rightSet.parent = leftSet; if (leftSet.rank == rightSet.rank) { leftSet.rank++; } } } /** * Query two elements whether contained in the same set. */ public boolean isConnected(T left, T right) { if (left == null || right == null) { return false; } Node leftNode = nodeMap.get(left); if (leftNode == null) { return false; } Node rightNode = nodeMap.get(right); if (rightNode == null) { return false; } if (leftNode == rightNode) { return true; } return findSet(leftNode) == findSet(rightNode); } public Collection> toSets() { Map, Set> setMap = new HashMap<>(); for (Map.Entry> entry : nodeMap.entrySet()) { setMap.computeIfAbsent(findSet(entry.getValue()), k -> new HashSet<>()) .add(entry.getKey()); } return setMap.values(); } public void show() { toSets().forEach(System.out::println); } /** * Find the set that contains the node, actual is the first node of the set. *

* Backtracking with path compression. */ private Node findSet(Node node) { if (node != node.parent) { node.parent = findSet(node.parent); } return node.parent; } private static void checkNotNull(Object obj, String msg) { if (obj == null) { throw new NullPointerException(msg + " must be not null"); } } static class Node { int rank; Node parent; Node() { parent = this; } } }