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 from 0
to N-1
. How does iz[]
help us know the number of elements in the set?
Can someone clarify this for me?
First understand that id
is a forest. id[i]
is the parent of i
. If id[i] == i
it means that i
is a root.
For some root i
(where id[i] == i
) then iz[i]
is the number of elements in the tree rooted at 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;
}
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.
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 once i
is a root node)
iz[]
contains integers from 0
to N-1
. How does iz[]
help us know the number of elements in the set?
There is a transcription error in the code:
for(int i = 0; i < id.length; i++)
{
iz[i] = i; // WRONG
id[i] = i;
}
This is the correct version:
for(int i = 0; i < id.length; i++)
{
iz[i] = 1; // RIGHT
id[i] = i;
}
iz[i]
is the number of elements for a tree rooted at i
(or if i
is not a root then iz[i]
is undefined). So it should be initialized to 1
, not i
. Initially each element is a seperate "singleton" tree of size 1
.
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:
/**
* Returns the component identifier for the component containing site <tt>p</tt>.
* @param p the integer representing one site
* @return the component identifier for the component containing site <tt>p</tt>
* @throws java.lang.IndexOutOfBoundsException unless 0 <= p < N
*/
public int find(int p) {
int root = p;
while (root != id[root])
root = id[root];
while (p != root) {
int newp = id[p];
id[p] = root;
p = newp;
}
return root;
}
Reference:
http://algs4.cs.princeton.edu/15uf/WeightedQuickUnionPathCompressionUF.java.html
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.
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.