From 01b0296dad7b601605ed6b34ad00a77226bbb907 Mon Sep 17 00:00:00 2001 From: yangxf Date: Fri, 15 Mar 2019 10:03:42 +0800 Subject: [PATCH] added an implementation of disjoint-set --- .../java/com/dataStructures/DisjointSet.java | 136 ++++++++++++++++++ .../com/dataStructures/DisjointSetTest.java | 34 +++++ 2 files changed, 170 insertions(+) create mode 100644 src/main/java/com/dataStructures/DisjointSet.java create mode 100644 src/test/java/com/dataStructures/DisjointSetTest.java diff --git a/src/main/java/com/dataStructures/DisjointSet.java b/src/main/java/com/dataStructures/DisjointSet.java new file mode 100644 index 00000000..c2a59c32 --- /dev/null +++ b/src/main/java/com/dataStructures/DisjointSet.java @@ -0,0 +1,136 @@ +package src.main.java.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. + * + * @author yangxf + */ +public class DisjointSet implements Serializable { + private static final long serialVersionUID = 3134700471905625636L; + + private Map> nodeMap = new HashMap<>(); + + /** + * Add an element to the disjoint-set forests as a set. + */ + public void makeSet(T element) { + checkNotNull(element, "element"); + 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, "element"); + checkNotNull(right, "element"); + + 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; + } + } + +} \ No newline at end of file diff --git a/src/test/java/com/dataStructures/DisjointSetTest.java b/src/test/java/com/dataStructures/DisjointSetTest.java new file mode 100644 index 00000000..5f13190d --- /dev/null +++ b/src/test/java/com/dataStructures/DisjointSetTest.java @@ -0,0 +1,34 @@ +package src.test.java.com.dataStructures; + +import org.junit.Test; +import src.main.java.com.dataStructures.DisjointSet; + +import static org.junit.Assert.*; + +public class DisjointSetTest { + @Test + public void test() { + DisjointSet set = new DisjointSet<>(); + + set.makeSet("flink"); + set.makeSet("c++"); + set.makeSet("java"); + set.makeSet("py"); + set.makeSet("spark"); + + set.union("java", "c++"); + + assertTrue(set.isConnected("java", "c++")); + assertFalse(set.isConnected("java", "py")); + + set.union("c++", "py"); + assertTrue(set.isConnected("java", "py")); + + set.makeSet("lisp"); + set.union("lisp", "py"); + + assertTrue(set.isConnected("c++", "lisp")); + + set.show(); + } +} \ No newline at end of file