For the implementation of parallel query set based on basic data type, please refer to QuickUnion.
The underlying implementation of user-defined object and query set: linked list + Map mapping [the linked list here is represented as a tree]
Logically: multiple trees from bottom to top.
The array can no longer meet the requirements of user-defined objects and query sets. You can define an internal class Node to form a Node node to encapsulate each user-defined object. The rank attribute of tree height is provided here. The relative balance of the tree is controlled through rank to improve the efficiency of query set.
private static class Node<V>{ V value; Node<V> parent = this; //During initialization, its parent node is itself int rank = 1; //During initialization, the tree height is 1 }
Provide initSet method to initialize and query the set:
public void initSet(V v) { if(nodes.containsKey(v)) return; nodes.put(v, new Node<>(v)); //One v corresponds to one node }
After the instantiated object calls the initSet method, each user-defined object forms its own set:
Define the private findRoot method: continuously probe from the given node until the root node is found. The probe process uses the path halving optimization strategy to improve efficiency.
private Node<V> findRoot(V v){ Node<V> node = nodes.get(v); //Get the Node corresponding to v if(node == null) return null; //Probe from node to root node while(!Objects.equals(node.value, node.parent.value)) { node = node.parent.parent; //Path halving optimization is used } return node; }
Define the find method: find the set to which the user-defined object belongs, and the root node of the node encapsulating the user-defined object is its set.
public V find(V v) { Node<V> node = findRoot(v); //Get the root node where v is located return node == null ? null : node.value; }
Define union method: for a given two custom objects, merge and encapsulate the two nodes of the custom object. First get the root node of the two nodes and graft the root node.
Node<V> rt1 = findRoot(v1); Node<V> rt2 = findRoot(v2)
There are three situations:
(1) The height of the left tree is lower than that of the right tree. At this time, the root node of the left tree is directly grafted to the root node of the right tree, and the overall height of the tree remains unchanged.
(2) The height of the left tree is higher than that of the right tree. At this time, the root node of the right tree is directly grafted to the root node of the left tree, and the overall height of the tree remains unchanged.
(3) The height of the tree on the left is equal to the height of the tree on the right. By default, the root node of the tree on the right is grafted to the root node of the tree on the left, and the overall height of the tree is increased by 1.
Code implementation in three cases:
if(rt1.rank < rt2.rank) { //Tree height with rt1 as root node < tree height with rt2 as root node rt1.parent = rt2; //Grafting from rt1 to rt2 }else if(rt1.rank > rt2.rank) { //Tree height with rt1 as root node > tree height with rt2 as root node rt2.parent = rt1; //Graft rt2 to rt1 }else { //Tree height with rt1 as root node = tree height with rt2 as root node rt2.parent = rt1; //Default rt2 grafted to rt1 rt1.rank += 1; //Update rt2 the tree height }
Define isTogether method: judge whether a given two objects belong to the same set. If the root node of the node encapsulating the user-defined object is the same, it belongs to the same set, otherwise it does not belong to different sets.
public boolean isTogether(V v1, V v2) { return Objects.equals(find(v1), find(v2)); }
Since then, the general concurrent query set has been completed, and the basic data types and user-defined objects can be used.
Provide complete code blocks for reference:
import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * Underlying implementation: linked list + Map mapping [the linked list here is represented as a simple tree] logically: multiple trees from bottom to top * @author Asus * * @param <V> Incoming custom object */ public class GenericUnionFind<V> { private Map<V, Node<V>> nodes = new HashMap<>(); //Initialize and query set public void initSet(V v) { if(nodes.containsKey(v)) return; nodes.put(v, new Node<>(v)); //One v corresponds to one node } //Returns the root node of v public V find(V v) { Node<V> node = findRoot(v); //Get the root node where v is located return node == null ? null : node.value; } public void union(V v1, V v2) { //Get the root node of v1 and v2 Node<V> rt1 = findRoot(v1); Node<V> rt2 = findRoot(v2); if(rt1 == null || rt2 == null) return; if(rt1.equals(rt2)) return; //In the same set, there is no need to merge if(rt1.rank < rt2.rank) { //Tree height with rt1 as root node < tree height with rt2 as root node rt1.parent = rt2; //Grafting from rt1 to rt2 }else if(rt1.rank > rt2.rank) { //Tree height with rt1 as root node > tree height with rt2 as root node rt2.parent = rt1; //Graft rt2 to rt1 }else { //Tree height with rt1 as root node = tree height with rt2 as root node rt2.parent = rt1; //Default rt2 grafted to rt1 rt1.rank += 1; //Update rt2 the tree height } } //Judge whether V1 and V2 belong to the same set public boolean isTogether(V v1, V v2) { return Objects.equals(find(v1), find(v2)); } //Find root node private Node<V> findRoot(V v){ Node<V> node = nodes.get(v); //Get the Node corresponding to v if(node == null) return null; //Probe from node to root node while(!Objects.equals(node.value, node.parent.value)) { node = node.parent.parent; //Path halving optimization is used } return node; } //Define node private static class Node<V>{ V value; int rank = 1; //The default tree height with itself as the root node is 1 Node<V> parent = this; //The default parent node is itself Node(V v){ this.value = v; } } }
End