AdSense

Showing posts with label Disjoint Set. Show all posts
Showing posts with label Disjoint Set. Show all posts

Sunday, January 18, 2015

The amazing maze

The idea came from a FB interview question: design a maze. So I googled, and found tremendous solutions. Mainly there are three ways to design a maze:

  • Depth-first search;
  • Randomized Kruskal's algorithm;
  • Randomized Prim's algorithm;
  • and so on.

See Wikipedia for more information.

The goal here is to design a "perfect" maze:




  • There are no cycles;
  • There is a unique path from the start cell in the maze to the end cell. 

Here I use randomized Kruskal's algorithm with a disjoint set data structure to perform union method.  It works in the following way:

  1. Create a list of all walls that potentially can be destroyed;
  2. Randomly choose a wall index;
  3. Union the two adjacent cells that are separated by the wall;
  4. Repeat until all cells are in the same set.


The union method acts like knocking down the wall, i.e., if two cells are in the same set, they are connected. When all cells are in the same set, there must be one path from the start cell to the end cell. Moreover, since every time we union two cells that are in different sets, there is no path between the cell before union, and since after the union, no other wall will be knocked down between these two cells, so there will be a unique path from any cell to another cell in the maze. Thus fulfill the "perfect" maze requirement.


public class Maze {
 private int[] grid;
 private int rows;
 private int columns;
 
 private Maze(int rows, int columns) {
  //using 1D array to represent cells
  //index / columns = row in the maze
  //index % columns = col in the maze
  this.grid = new int[rows * columns];
  //one cell is surrounded by walls in four directions
  //initially create cells with all walls up
  Arrays.fill(grid, UP | RIGHT | DOWN | LEFT);
  this.rows = rows;
  this.columns = columns;
 }
 
 private static final int UP = 1;
 private static final int RIGHT = 2;
 private static final int  DOWN = 4;
 private static final int LEFT = 8;
 
 public static Maze createRandomMaze(int rows, int columns) {
  Maze maze = new Maze(rows, columns);
  //create all walls that potentially can be broken
  List walls = new ArrayList();
  for (int row = 0; row < rows; row++) {
   for (int col = 0; col < columns; col++) {
    if (row > 0) 
     //cell = row * columns + col
     //cell / columns = row
     //cell % columns = col
     // represent the grid in the maze
     //the upper wall of the lower cell is the lower wall of the upper cell
     // the left wall of the right cell is the right wall of the left cell
     //so we only need to consider two directions
     walls.add(new Wall(row * columns + col, UP));
    if (col > 0)
     walls.add(new Wall(row * columns + col, LEFT));
   }
  }
  
  DisjointSet diset = new ArrayDisjointSet(rows * columns);
  //Object for generating random numbers
  Random rand = new Random();
  while (diset.size() > 1) {
   //get an index randomly
   int wallIndex = rand.nextInt(walls.size());
   int cell1 = walls.get(wallIndex).cell;
   int cell2 = (walls.get(wallIndex).direction == UP) ?
     cell1 - columns ://choose the cell and the one above it, break the upper wall
      cell1 - 1;//choose the one left to it, break the left wall
   //if there is no path between two cells
   //i.e., they are not in the same set
   if (diset.find(cell1) != diset.find(cell2)) {
    if (walls.get(wallIndex).direction == UP) {
     //break the upper wall of cell1 
     //which is also the lower wall of cells2
     maze.grid[cell1] ^= UP;
     maze.grid[cell2] ^= DOWN;
    }
    else {
     maze.grid[cell1] ^= LEFT;
     maze.grid[cell2] ^= RIGHT;
    }
    diset.union(cell1, cell2);
   }
   //the wall is knocked down, dead, disappeared, over...
   walls.remove(wallIndex);
  }
  return maze;
 }
 public static class Wall {
  private final int cell;
  private final int direction;
  public Wall(int cell, int direction) {
   this.cell = cell;
   this.direction = direction;
  }
 }
}

The result of a 30 by 30 grids:




The source code can be found on my Github: https://github.com/shirleyyoung0812/mazeDFS.git

To people who devote their lives to the dream they have. 

Saturday, January 17, 2015

Disjoint Set Union/Find ADT

Definition


  • Set U = {a1, a2, ..., an}
  • Maintain a partition of U, a set of subsets of U {S1, S2, ..., Sk} such that:
    •    each pair of subsets Si and Sj are disjoint;
    •    together, the subsets cover U;
    •    each subset has a unique name. 
  • Union(a, b) creates a new subset which is the union of a and b;
  • Find(a) returns the representative member of a set: similar to find parent of a TreeNode in a tree; 
  • Thus Disjoint Set can be represented by an up-tree;
  • Disjoint set equivalence property: every element of a DS U/F structure belongs to exactly one set;
  • Dynamic equivalence property: the set of an element can change after execution of a union.



Up-Tree Union-Find Data Structure


Image source: http://courses.cs.washington.edu/courses/cse326/00wi/handouts/lecture18/sld015.htm

  • Each subset is an up-tree with its root as its representative member;
  • All members of a given set are nodes in that set's up-tree;
  • Not necessarily binary;
  • Find(a): traverse from the leaf to the root;
  • Union(a, b): union the root with the other;


Path Compression

Image source: http://courses.cs.washington.edu/courses/cse326/00wi/handouts/lecture18/sld034.htm
Points everything along the path of a find to the root;
Thus reduces the height of the entire access path to 1.


Union-by-rank 

Use an extra array to store the rank of a given node. Rank[a] is the depth of the tree rooted at a.
Image source: http://courses.cs.washington.edu/courses/cse326/00wi/handouts/lecture18/sld029.htm

Union(a, b): pick the shallower tree and point it at the deeper one.

Image source: http://courses.cs.washington.edu/courses/cse326/00wi/handouts/lecture18/sld031.htm
This can cut down on height of the new tree;
Find(a) complexity: a tree of height h must have at least 2 ^h nodes. Find(a) takes O(max height) time. Thus Find(a) takes O(log(n)) time.


Complexity of Union-by-rank path compression

Tarjan proved that the find operation on a set of n elements in a union with m union-by-rank subsets has worst complexity O(m * alpha(m,n)).

Implementation
This implementation uses an array.
The test data set: 16 integers range from 0 to 15.
After union, each set should contain 4 integers.


public class DisjointSets {
 //array[x] stores the root of the tree (representative of the set)
 //if array[x] is less than 0, that means x is the root of the tree (representative of the set)
 private int[] array;
 
 //initial number of elements;
 //also the initial number of disjoint sets
 //since every element is initially in its own set
 public DisjointSets(int numElements) {
  array = new int [numElements];
  //since every element is the root of its tree,
  //set each element in the array to negative
  Arrays.fill(array, -1);
 }


Every element is the root of the tree it belongs
Initial Array: every element is negative


public void union(int root1, int root2) {
  if (array[root2] < array[root1]) 
   array[root1] = root2; // root2 is taller; make root2 new root
  else {
   if (array[root1] == array[root2]) 
    array[root1]--;
   array[root2] = root1; // root1 equal or taller; make root1 new root
  }
 }



After first union
Array after first union
After second union

Array after second union


public int find(int x) {
  if (array[x] < 0)
   return x; //x is the root of the tree; return it
  else {
   //find the root of the tree
   array[x] = find(array[x]);
   return array[x];
  }
 }


Test code and result:

public static void main(String[] args) {
  int NumElements = 16;
  int NumInSameSet = 4;
  DisjointSets s = new DisjointSets(NumElements);
  int set1, set2;
  for (int k = 1; k < NumInSameSet; k *= 2) {
   for (int j = 0; j + k < NumElements; j += 2 * k) {
    set1 = s.find(j);
    System.out.println("set1: " + set1);
    set2 = s.find(j + k);
    System.out.println("set2: " + set2);
    s.union(set1, set2);
   }
  }
  for (int i = 0; i < NumElements; i++) {
   System.out.print(s.find(i) + "*");
   if (i % NumInSameSet == NumInSameSet - 1)
    System.out.println();
  }
  System.out.println(); 
 }