Skip to content

Commit

Permalink
DFS traversal
Browse files Browse the repository at this point in the history
  • Loading branch information
alexis779 committed Apr 4, 2022
1 parent 27570a3 commit 8c25c8f
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/main/java/graph/tree/traversal/Color.java
@@ -0,0 +1,7 @@
package graph.tree.traversal;

enum Color {
WHITE,
GRAY,
BLACK,
}
58 changes: 58 additions & 0 deletions src/main/java/graph/tree/traversal/DFSTraversal.java
@@ -0,0 +1,58 @@
package graph.tree.traversal;

import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

/**
* Simulate a Depth-First-Search Postorder traversal
* Use a LIFO queue where node elements are visited twice:
* - 1st time to explore the children top-down
* - 2nd time to provide a bottom-up node processing
*/
public class DFSTraversal implements Traversal {

public void traverse(List<List<Integer>> adjacency, NodeVisitor nodeVisitor) {
int N = adjacency.size();

// Use a color attribute to visit each node twice during the queue processing
// It also helps to discover connected components and detect loops
Color[] color = new Color[N];
for (int i = 0; i < N; i++) {
color[i] = Color.WHITE;
}

for (int i = 0; i < N; i++) {
if (color[i] == Color.WHITE) {
// find the connected component
traverse(adjacency, i, nodeVisitor, color);
}
}
}

private void traverse(List<List<Integer>> adjacency, int root, NodeVisitor nodeVisitor, Color[] color) {
Deque<Integer> deque = new LinkedList<>();
deque.addLast(root);

while (! deque.isEmpty()) {
// peek only on 1st visit, remove at the end of 2nd visit
int parent = deque.getLast();
List<Integer> c = adjacency.get(parent);
if (color[parent] == Color.WHITE) {
color[parent] = Color.GRAY;

// explore tree top-down
c.stream()
.filter(child -> color[child] == Color.WHITE) // avoid infinite loop
.forEach(deque::addLast);
} else {
// process node bottom up
nodeVisitor.visit(parent, c);

color[parent] = Color.BLACK;
// poll on 2nd visit
deque.removeLast();
}
}
}
}
7 changes: 7 additions & 0 deletions src/main/java/graph/tree/traversal/NodeVisitor.java
@@ -0,0 +1,7 @@
package graph.tree.traversal;

import java.util.List;

public interface NodeVisitor {
void visit(int parent, List<Integer> children);
}
7 changes: 7 additions & 0 deletions src/main/java/graph/tree/traversal/Traversal.java
@@ -0,0 +1,7 @@
package graph.tree.traversal;

import java.util.List;

public interface Traversal {
void traverse(List<List<Integer>> adjacency, NodeVisitor nodeVisitor);
}
35 changes: 35 additions & 0 deletions src/test/java/graph/tree/traversal/DFSTraversalTest.java
@@ -0,0 +1,35 @@
package graph.tree.traversal;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

import static java.util.Arrays.asList;

public class DFSTraversalTest {

private final static Logger LOG = LoggerFactory.getLogger(DFSTraversalTest.class);

private final DFSTraversal dfsTraversal = new DFSTraversal();

@Test
public void dfs() {
List<List<Integer>> adjacency = new ArrayList<>();
adjacency.add(0, asList(1));
adjacency.add(1, asList(2, 3));
adjacency.add(2, asList(4));
adjacency.add(3, asList());
adjacency.add(4, asList());

List<Integer> nodes = new ArrayList<>();
dfsTraversal.traverse(adjacency, (parent, children) -> {
nodes.add(parent);
LOG.info(String.format("%d: %s", parent, children));
});
Assertions.assertEquals(asList(3, 4, 2, 1, 0), nodes, "Post-order traversal");
}
}

0 comments on commit 8c25c8f

Please sign in to comment.