# Joint search set

#### Join search set is a tree data structure

The parallel query set is a tree type data structure. The parallel query set can efficiently perform the following operations:

- Query whether element p and element q belong to the same group
- Merge the group of element p and element q

## Joint query set structure

The parallel query set is also a tree structure, but this tree is different from the binary tree, red black tree and B tree we talked about earlier. The requirements of this tree are relatively simple:

- Each element uniquely corresponds to a node;
- Multiple elements in each group of data are in the same tree;
- There is no connection between the tree corresponding to the data in one group and the tree corresponding to the data in another group;
- Element has no rigid requirement of the child parent relationship in tree;

# 1. Implementation of parallel query set

## API design

## Implementation of UF(int N) construction method

- In the initial case, each element is in an independent group. Therefore, in the initial case, the data in the query set is divided into N groups by default;
- Initialize the array eleAndGroup;
- The index of the eleAndGroup array is regarded as the element stored in each node, and the value at each index of the eleAndGroup array is regarded as the group where the node is located. In the case of initialization, the value stored at the i index is i

## Implementation of union(int p,int q) merging method

- If p and q are already in the same group, there is no need to merge
- If p and q are not in the same group, you only need to modify the group identifier of all elements in the group of p element to the identifier of the group of q element
- Number of groups - 1

## code:

package main.java.Algorithms.tree; public class UF { //Record the node element and the identification of the group in which the element is located private int[] eleAndGroup; //Record and check the number of data groups in the set private int count; //Initialize and query set public UF(int N){ //The number of initialization packets. By default, there are N packets this.count = N; //Initialize eleAndGroup array this.eleAndGroup = new int[N]; //Initialize the element in eleAndGroup and the identifier of its group, make the index of eleAndGroup array as the element of each node of the query set, and make the value at each index (the identifier of the group in which the element is located) the index for (int i = 0; i < eleAndGroup.length; i++) { eleAndGroup[i] = i; } } //How many groups are there in the current query set public int count(){ return count; } //Identifier of the group in which the element p is located public int find(int p){ return eleAndGroup[p]; } //Judge and check whether element p and element q in the set are in the same group public boolean connected(int p,int q){ return find(p) == find(q); } //Merge the group of p element and the group of q element public void union(int p,int q){ //Judge whether the elements q and p are already in the same group. If they are already in the same group, the end method is OK if (connected(p,q)){ return; } //Find the identifier of the group where p is located int pGroup = find(p); //Find the identifier of the group where q is located int qGroup = find(q); //Merge group: change the group identifier of all elements in p's group into the identifier of q's group for (int i = 0; i < eleAndGroup.length; i++) { if (eleAndGroup[i]==pGroup){ eleAndGroup[i] = qGroup; } } //Number of groups - 1 this.count--; } }

## Test:

package main.java.Algorithms.tree; import java.util.Scanner; public class UFTest { public static void main(String[] args) { //Create and query set objects UF uf = new UF(5); System.out.println("By default, the query set includes:"+uf.count()+"Groups"); //Enter two elements to be merged from the console, call the union method to merge, observe the merged elements, and check whether the grouping in the set is reduced Scanner sc = new Scanner(System.in); while(true){ System.out.println("Please enter the first element to merge:"); int p = sc.nextInt(); System.out.println("Please enter the second element to merge:"); int q = sc.nextInt(); //Determine whether the two elements are already in the same group if (uf.connected(p,q)){ System.out.println(p+"Element and"+q+"The element is already in the same group"); continue; } uf.union(p,q); System.out.println("In the current query set, there are:"+uf.count()+"Groups"); } } }

# Case 1: computer network connection

- If each integer stored in the parallel query set represents a computer in a large computer network, we can detect whether two computers in the network are connected through connected(intp,int q)? If they are connected, they can communicate. If they are not connected, they cannot communicate. At this time, we can call union(int p,int q) to connect P and Q, so that the two computers can communicate.
- Generally, for network data such as computers, we require that every two data in the network are connected, that is, we need to call the union method many times to connect all data in the network. In fact, we can easily conclude that if we want to connect all data in the network, we need to call the union method at least N-1 times, However, because our union method uses the for loop to traverse all elements, it is obvious that the time complexity of the merging algorithm we implemented before is O(N^2). If we want to solve large-scale problems, it is not appropriate, so we need to optimize the algorithm.

# 2. UF_Tree algorithm optimization

In order to improve the performance of the union algorithm, we need to redesign the implementation of the find method and the union method. At this time, we need to reset the meaning of the eleAndGourp array in our previous data structure:

## eleAndGourp array

- We still use the index of eleAndGroup array as the element of a node;
- The value of eleAndGroup[i] is no longer the group ID of the current node, but the parent node of the node;

## API design

## Implementation of find(int p) query method

- Judge whether the parent node eleAndGroup[p] of the current element p is itself. If it is itself, it is proved to be the root node;
- If the parent node of the current element p is not itself, let p=eleAndGroup[p], and continue to find the parent node of the parent node until the root node is found;

## Implementation of union(int p,int q) merging method

- Find the root node of the tree where the p element is located
- Find the root node of the tree where the q element is located
- If p and q are already in the same tree, there is no need to merge;
- If p and q are not in the same group, you only need to set the parent node of the root node of the tree where p element is located as the root node of q element;
- Number of groups - 1

## code

package main.java.Algorithms.tree; public class UF_Tree { //Record the node element and the identification of the group in which the element is located private int[] eleAndGroup; //Record and check the number of data groups in the set private int count; //Initialize and query set public UF_Tree(int N){ //The number of initialization packets. By default, there are N packets this.count = N; //Initialize eleAndGroup array this.eleAndGroup = new int[N]; //Initialize the element in eleAndGroup and the identifier of its group, make the index of eleAndGroup array as the element of each node of the query set, and make the value at each index (the identifier of the group in which the element is located) the index for (int i = 0; i < eleAndGroup.length; i++) { eleAndGroup[i] = i; } } //How many groups are there in the current query set public int count(){ return count; } //Judge and check whether element p and element q in the set are in the same group public boolean connected(int p,int q){ return find(p) == find(q); } //Identifier of the group in which the element p is located public int find(int p){ while(true){ if (p == eleAndGroup[p]){ return p; } p = eleAndGroup[p]; } } //Merge the group of p element and the group of q element public void union(int p,int q){ //Find the root node of the tree corresponding to the group of p and q elements int pRoot = find(p); int qRoot = find(q); //If p and q are already in the same group, there is no need to merge if (pRoot==qRoot){ return; } //Let the parent node of the root node of the tree where p is located be the root node of the tree where q is located eleAndGroup[pRoot] = qRoot; //Number of groups - 1 this.count--; } }

## Optimized performance analysis

- Our optimized algorithm union still needs to call the union method at least N-1 times if we want to connect all the data in the union query set. However, we find that there is no for loop in the union method, so the time complexity of the union algorithm changes from O(N^2) to O(N).
- But this algorithm still has problems, because we modified not only the union algorithm, but also the find algorithm. The time complexity of our modified find algorithm is O(1) in any case, but the modified find algorithm is O(N) in the worst case:

The find method is invoked in the union method, so the time complexity of the union algorithm is still O(N^2) in the worst case.

# 3. Optimized path compression

## analysis:

- UF_ In the worst case, the time complexity of the union algorithm in tree is O(N^2). The main problem is that in the worst case, the depth of the tree is the same as the size of the array. If we can use some algorithms to make the depth of the generated tree as small as possible during merging, we can optimize the find method.
- Previously, in the union algorithm, when merging trees, we connected any tree to another tree. This merging method is more violent. If we record the size of each tree in the merge set, and then connect the smaller tree to the larger tree each time, we can reduce the depth of the tree.

As long as we ensure that the small tree can be merged into the big tree every time, we can compress the path of the merged new tree, which can improve the efficiency of the find method.

To fulfill this requirement, we need another array to record the number of elements in the tree corresponding to each root node, and we need some code to adjust the values in the array.

## API design

## code

package main.java.Algorithms.tree; public class UF_Tree_Weighted { //Record the node element and the identification of the group in which the element is located private int[] eleAndGroup; //Record and check the number of data groups in the set private int count; //It is used to store the number of nodes saved in the tree corresponding to each root node private int[] sz; //Initialize and query set public UF_Tree_Weighted(int N){ //The number of initialization packets. By default, there are N packets this.count = N; //Initialize eleAndGroup array this.eleAndGroup = new int[N]; //Initialize the element in eleAndGroup and the identifier of its group, make the index of eleAndGroup array as the element of each node of the query set, and make the value at each index (the identifier of the group in which the element is located) the index for (int i = 0; i < eleAndGroup.length; i++) { eleAndGroup[i] = i; } this.sz = new int[N]; //By default, the value at each index in sz is 1 for (int i = 0; i < sz.length; i++) { sz[i] = 1; } } //How many groups are there in the current query set public int count(){ return count; } //Judge and check whether element p and element q in the set are in the same group public boolean connected(int p,int q){ return find(p) == find(q); } //Identifier of the group in which the element p is located public int find(int p){ while(true){ if (p == eleAndGroup[p]){ return p; } p = eleAndGroup[p]; } } //Merge the group of p element and the group of q element public void union(int p,int q){ //Find the root node of the tree corresponding to the group of p and q elements int pRoot = find(p); int qRoot = find(q); //If p and q are already in the same group, there is no need to merge if (pRoot==qRoot){ return; } //To determine whether the tree size corresponding to proot or qroot is large, you need to merge the smaller trees into the larger trees if (sz[pRoot]<sz[qRoot]){ eleAndGroup[pRoot] = qRoot; sz[qRoot]+=sz[pRoot]; }else{ eleAndGroup[qRoot] = pRoot; sz[pRoot]+= sz[qRoot]; } //Number of groups - 1 this.count--; } }

# Case 2: unblocked project

## Requirements:

- A province investigates the urban traffic conditions and obtains the statistical table of existing urban roads, which lists the cities and towns directly connected by each road. The goal of the provincial government's "unblocked project" is to enable any two cities and towns in the province to realize traffic (but there is not necessarily a direct road connection, as long as they can reach each other indirectly through the road). How many roads need to be built at least?
- There is a trffic in our test data folder_ Project.txt file, which is the statistical table of Zhengzheng road. The following is the interpretation of the data:

There are 20 cities in total. At present, 7 roads have been modified. How many more roads need to be built to connect all the 20 cities?

## Problem solving ideas:

- Create a query set UF_Tree_Weighted(20);
- Call Union (0,1), Union (6,9), Union (3,8), Union (5,11), Union (2,12), Union (6,10) and Union (4,8) respectively, indicating that it has been built

Roads connect the corresponding cities; - If all the cities are connected, the number of remaining groups in the query set is 1, and all the cities are in one tree. Therefore, you only need to obtain the remaining number in the current query set and subtract 1, which is the number of roads to be built;

## code:

package main.java.Algorithms.tree; import java.io.BufferedReader; import java.io.InputStreamReader; public class Traffic_Project_Test { public static void main(String[] args) throws Exception{ //Build a buffered read stream BufferedReader BufferedReader br = new BufferedReader(new InputStreamReader(Traffic_Project_Test.class.getClassLoader().getResourceAsStream("traffic_project.txt"))); //Read the first row of data 20 int totalNumber = Integer.parseInt(br.readLine()); //Build a union query object main.java.Algorithms.tree.UF_Tree_Weighted uf = new main.java.Algorithms.tree.UF_Tree_Weighted(totalNumber); //Read the second row of data 7 int roadNumber = Integer.parseInt(br.readLine()); //Cycle 7 roads for (int i=1;i<=roadNumber;i++){ String line = br.readLine();//0 1 String[] str = line.split(" "); int p = Integer.parseInt(str[0]); int q = Integer.parseInt(str[1]); //Call and query the union method of the set object to connect the two cities uf.union(p,q); } //Get the number of groups in the current query set - 1 to get the number of roads to be built int roads = uf.count()-1; System.out.println("It also needs to be built"+roads+"This road can realize the smooth project"); } }