Depth First Search

 We return today to graph search. Last time we saw breadth-first search, today we're going to do depth-first search. It's a simple algorithm, but you can do lots of cool things with it. DFS is often used to explore the whole graph, and so we are going to see how to do that today. So high-level description is we're going to just recursively explore the graph, backtracking as necessary, kind of like how you solve a maze.

Let’s now consider a real world application of the DFS algorithm.

Number of Closed Islands - Problem Definition

Given a 2D grid consists of 0s (land) and 1s (water).  An island is a maximal 4-directionally connected group of 0s and a closed island is an island totally (all left, top, right, bottom) surrounded by 1s.

Return the number of closed islands.


Output: 5


Output: 4

Note that closed islands are colored in the tables above.


Output: 2


Output: 1

Number of Closed Islands - Approach

We use the table d to keep track of the discovery state of each block. We initialize each entry of the d table to false, updating it to true when we first discover that particular cell. We start off with the first undiscovered land block, finding all the reachable land blocks from that. If any of the reachable land blocks hit the border of the map area, then it is not surrounded by water, hence the procedure returns false. Otherwise, the land area reachable from the starting position is surrounded by water, thus true is returned. Each time we encounter a closed island, we increment the variable k by one and finally return it. Also note that we should cover all the reachable land blocks from the starting block in one pass. What follows is the pseudocode of the procedure just described.

**Input:** Map area represented by an mn matrix     
**Output:** Number of closed islands.  
CLOSEDISLAND(G)  
++ mG.length  
++ nG[1].length  
++ let d be a m×n table  
++ `for` i=1 to m  
++ ++ `for`  j=1 to n  
++ ++ ++ d[i][j]false  

++ let k0  
++ `for` i=1 to m  
++ ++ `for`  j=1 to n  
++ ++ ++ `if` G[i][j]==0 and d[i][j]==false  
++ ++ ++ ++ `if` CLOSEDISLANDVISIT(G,d,i,j)   
++ ++ ++ ++ ++ kk+1  

++ `return` k  


**Input:** Map area represented by an mn matrix, a discovery matrix d with dimensions mn and x and y coordinates of the land block.       
**Output:** Whether the island rooted at the block lies in the position i and j is a closed island.   
CLOSEDISLANDVISIT(G,d,i,j)   
++ d[i][j]true  
++ resulti1 and id.length and j1 and jd[1].length  
++ `for` `each` pM  
++ ++ ri+p[1]  
++ ++ cj+p[2]  
++ ++ `if` r>0 and rG.length and c>0 and cG[1].length and G[r][c]==0 and d[r][c]==false  
++ ++ ++ resultCLOSEDISLANDVISIT(G,d,r,c) and result   
++ `return` result

A sample implementation of the algorithm for finding the number of closed islands is given below.

class ClosedIslands {
private static int[][] MOVES = { { 0, 1 }, { 0, -1 }, { -1, 0 }, { 1, 0 } };
ClosedIslands() {
throw new AssertionError();
}
public static void main(String[] args) {
final int[][] grid1 = new int[][] { { 1, 1, 1, 1, 1, 1, 1, 0 }, { 1, 0, 0, 0, 0, 1, 1, 0 },
{ 1, 0, 1, 0, 1, 1, 1, 0 }, { 1, 0, 0, 0, 0, 1, 0, 1 }, { 1, 1, 1, 1, 1, 1, 1, 0 } };
final int islands = closedIsland(grid1);
assert islands == 2;
}
static int closedIsland(int[][] grid) {
final int m = grid.length;
final int n = grid[0].length;
final boolean[][] d = new boolean[m][n];
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
d[i][j] = false;
int k = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 0 && !d[i][j]) {
if (closedIslandVisit(grid, d, i, j))
k = k + 1;
}
}
}
return k;
}
private static boolean closedIslandVisit(int[][] grid, boolean[][] d, int i, int j) {
d[i][j] = true;
boolean result = i != 0 && i != d.length - 1 && j != 0 && j != grid[0].length - 1;
for (int[] m : MOVES) {
final int r = i + m[0];
final int c = j + m[1];
if (r >= 0 && r < grid.length && c >= 0 && c < grid[0].length && grid[r][c] == 0 && !d[r][c])
result = result & closedIslandVisit(grid, d, r, c);
}
return result;
}
}

Note that in the implementation section we used the non-short circuiting and operator, instead of the widely used short circuiting and operator. As mentioned before, we need to evaluate the blocks greedily, without immediately returning when we encounter a block which lies on the border of the map area. If you use the short circuiting and operator, then java would not bother to evaluate the right-hand operand if the left-hand operand evaluates to false.

Analysis

We have O(mn) blocks in our map area. For each block, we find its four adjacent cells, check whether that position lies within the map area, and call the procedure recursively with the new coordinates if the current block is an undiscovered land area. Then we concatenate the results. This attributes to a constant amount of time for each block, given the number of adjacent cells is 4. Thus, our approach of finding the number of closed islands involves  O(mn) time. 

Since we use the d table with dimensions m×n to keep track of discovery status of each block, our solution incurs O(mn) memory.

References

Comments

Popular posts from this blog

Introducing Java Reactive Extentions in to a SpringBoot Micro Service

Combining the emissions of multiple Observables together using RxJava Zip operator in a Spring Boot Micro service

RabbitMQ Transport in WSO2 ESB