package uf;

public class UnionFind {

  private int[] link;
  private int[] rank;
  private int numClasses;
  
  public UnionFind(int n) {
    if (n < 0) throw new IllegalArgumentException();
    this.link = new int[n];
    for (int i = 0; i < n; i++) this.link[i] = i;
    this.rank = new int[n];
    this.numClasses = n;
  }
  
  public int numClasses() {
    return this.numClasses;
  }
  
  public int find(int i) {
    if (i < 0 || i >= this.link.length)
      throw new ArrayIndexOutOfBoundsException(i);
    int p = this.link[i];
    if (p == i) return i;
    int r = this.find(p);
    this.link[i] = r; // compression de chemin
    return r;
  }
  
  public void union(int i, int j) {
    int ri = this.find(i);
    int rj = this.find(j);
    if (ri == rj) return; // déjà dans la même classe
    this.numClasses--;
    if (this.rank[ri] < this.rank[rj])
      this.link[ri] = rj;
    else {
      this.link[rj] = ri;
      if (this.rank[ri] == this.rank[rj])
        this.rank[ri]++;
    }
  }
  
  public boolean sameClass(int i, int j) {
    return this.find(i) == this.find(j);
  }
 
  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append(this.numClasses);
    for (int i = 0; i < this.link.length; i++)
      sb.append("[" + i + "->" + this.link[i] + "]");
    return sb.toString();
  }
  
}

class TestUnionFind {
  
  public static void main(String[] args) {
    UnionFind uf = new UnionFind(8);
    System.out.println(uf);
    assert (uf.find(3) == 3);
    uf.union(1, 5);
    System.out.println(uf);
    assert (uf.find(1) == uf.find(5));
    assert (uf.find(1) != uf.find(2));
    uf.union(0, 7);
    uf.union(6, 2);
    uf.union(2, 4);
    uf.union(0, 3);
    uf.union(1, 7);
    System.out.println(uf);
    for (int i = 0; i < 8; i++)
      assert (uf.sameClass(i, (i == 0 || i % 2 == 1) ? 0 : 2));
    System.out.println(uf);
    System.out.println("TestUnionFind OK");
  }
  
}