There is a "Weighted Quick-Union with Path Compression" algorithm.
The code:
public class WeightedQU
{
private int[] id;
private int[] iz;
public WeightedQU(int N)
{
id = new int[N];
iz = new int[N];
for(int i = 0; i < id.length; i++)
{
iz[i] = i;
id[i] = i;
}
}
public int root(int i)
{
while(i != id[i])
{
id[i] = id[id[i]]; // this line represents "path compression"
i = id[i];
}
return i;
}
public boolean connected(int p, int q)
{
return root(p) == root(q);
}
public void union(int p, int q) // here iz[] is used to "weighting"
{
int i = root(p);
int j = root(q);
if(iz[i] < iz[j])
{
id[i] = j;
iz[j] += iz[i];
}
else
{
id[j] = i;
iz[i] += iz[j];
}
}
}
Questions:
How does the path compression work?
id[i] = id[id[i]]
means that we reach only the second ancester of our node, not the root.iz[]
contains integers from0
toN-1
. How doesiz[]
help us know the number of elements in the set?
Can someone clarify this for me?
Question 1. It is not right to say that the line id[i] = id[id[i]]; only reaches the second ancestor of the root.You will realize that while loop while(i != id[i]) stops only when the node i is pointing at the root i.e when i == id[i].By this time we shall have pointed the node to the root using the line id[i] = id[id[i]]; where the inner id[i] is the root.
Question 2.
You are wrong to initialize iz[i] = i; actually it should be iz[i] = 1; meaning, each and every node size is initialized by 1 at the beginning since they are of size 1. In the union function you realize that we have the lines iz[j] += iz[i]; and iz[i] += iz[j]; which updates the size of the root node to be the sum of the sizes of the two components joined together. This efficiently updates the nodes sizes.
id[i] = id[id[i]]; // this line represents "path compression"
The above code is "Simpler one-pass variant" as mentioned in the slide of Union Find (Algorithms, Part I by Kevin Wayne and Robert Sedgewick). Therefore your guess for question 1 is correct. Each examined node points to its grandparent.
To make each examined node points to the root we will need two-pass implementation:
Reference: http://algs4.cs.princeton.edu/15uf/WeightedQuickUnionPathCompressionUF.java.html
First understand that
id
is a forest.id[i]
is the parent ofi
. Ifid[i] == i
it means thati
is a root.For some root
i
(whereid[i] == i
) theniz[i]
is the number of elements in the tree rooted ati
.As we are ascending the tree to find the root we move nodes from their parents to their grandparents. This partially flattens the tree. Notice that this operation doesn't change which tree the node is a member of, this is all we are interested in. This is the path compression technique.
(You did notice the loop right?
while(i == id[i])
terminates oncei
is a root node)There is a transcription error in the code:
This is the correct version:
iz[i]
is the number of elements for a tree rooted ati
(or ifi
is not a root theniz[i]
is undefined). So it should be initialized to1
, noti
. Initially each element is a seperate "singleton" tree of size1
.One more thing to be noted here:
While finding the root when we are making
id[i]=id[id[i]]
i.e; making i under its grand parent-then size of
id[i]
will decrease by size of i i,e;iz[id[i]]-=iz[i]
Now this makes code perfectly correct.
I am not sure about this but intuitively i feel, Its absence does not cause problems because we are always comparing size of the roots.