added an implementation of disjoint-set
This commit is contained in:
parent
7842c01bed
commit
01b0296dad
136
src/main/java/com/dataStructures/DisjointSet.java
Normal file
136
src/main/java/com/dataStructures/DisjointSet.java
Normal file
@ -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.
|
||||||
|
* <p>
|
||||||
|
* Actually, the instance of {@link DisjointSet} is a disjoint-set forests.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Disjoint-set operations:
|
||||||
|
* <p>
|
||||||
|
* 1. quickly unites two sets into a new set, requiring O(1) time.
|
||||||
|
* <p>
|
||||||
|
* 2. quickly query two elements whether contained in the same set, requiring about O(1) time.
|
||||||
|
*
|
||||||
|
* @author yangxf
|
||||||
|
*/
|
||||||
|
public class DisjointSet<T> implements Serializable {
|
||||||
|
private static final long serialVersionUID = 3134700471905625636L;
|
||||||
|
|
||||||
|
private Map<T, Node<T>> 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.
|
||||||
|
* <p>
|
||||||
|
* Rank is an upper bound on the height of node.
|
||||||
|
*/
|
||||||
|
public void union(T left, T right) {
|
||||||
|
checkNotNull(left, "element");
|
||||||
|
checkNotNull(right, "element");
|
||||||
|
|
||||||
|
Node<T> 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<T> 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<T> leftNode = nodeMap.get(left);
|
||||||
|
if (leftNode == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node<T> rightNode = nodeMap.get(right);
|
||||||
|
if (rightNode == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftNode == rightNode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return findSet(leftNode) == findSet(rightNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Set<T>> toSets() {
|
||||||
|
Map<Node<T>, Set<T>> setMap = new HashMap<>();
|
||||||
|
for (Map.Entry<T, Node<T>> 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.
|
||||||
|
* <p>
|
||||||
|
* Backtracking with path compression.
|
||||||
|
*/
|
||||||
|
private Node<T> findSet(Node<T> 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<T> {
|
||||||
|
int rank;
|
||||||
|
Node<T> parent;
|
||||||
|
|
||||||
|
Node() {
|
||||||
|
parent = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
34
src/test/java/com/dataStructures/DisjointSetTest.java
Normal file
34
src/test/java/com/dataStructures/DisjointSetTest.java
Normal file
@ -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<Object> 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user