tom

Added iterative DFS algorithm.

1 +package org.onlab.graph;
2 +
3 +import java.util.HashMap;
4 +import java.util.HashSet;
5 +import java.util.Map;
6 +import java.util.Set;
7 +import java.util.Stack;
8 +
9 +/**
10 + * DFS graph search algorithm implemented via iteration rather than recursion.
11 + */
12 +public class DepthFirstSearch<V extends Vertex, E extends Edge<V>>
13 + extends AbstractGraphPathSearch<V, E> {
14 +
15 + /**
16 + * Graph edge types as classified by the DFS algorithm.
17 + */
18 + public static enum EdgeType {
19 + TREE_EDGE, FORWARD_EDGE, BACK_EDGE, CROSS_EDGE
20 + }
21 +
22 + @Override
23 + public SpanningTreeResult search(Graph<V, E> graph, V src, V dst,
24 + EdgeWeight<V, E> weight) {
25 + checkArguments(graph, src, dst);
26 +
27 + // Prepare the search result.
28 + SpanningTreeResult result = new SpanningTreeResult(src, dst);
29 +
30 + // The source vertex has cost 0, of course.
31 + result.updateVertex(src, null, 0.0, true);
32 +
33 + // Track finished vertexes and keep a stack of vertexes that have been
34 + // started; start this stack with the source on it.
35 + Set<V> finished = new HashSet<>();
36 + Stack<V> stack = new Stack<>();
37 + stack.push(src);
38 +
39 + while (!stack.isEmpty()) {
40 + V vertex = stack.peek();
41 + if (vertex.equals(dst)) {
42 + // If we have reached our destination, bail.
43 + break;
44 + }
45 +
46 + double cost = result.cost(vertex);
47 + boolean tangent = false;
48 +
49 + // Visit all egress edges of the current vertex.
50 + for (E edge : graph.getEdgesFrom(vertex)) {
51 + // If we have seen the edge already, skip it.
52 + if (result.isEdgeMarked(edge)) {
53 + continue;
54 + }
55 +
56 + // Examine the destination of the current edge.
57 + V nextVertex = edge.dst();
58 + if (!result.hasCost(nextVertex)) {
59 + // If this vertex have not finished this vertex yet,
60 + // not started it, then start it as a tree-edge.
61 + result.markEdge(edge, EdgeType.TREE_EDGE);
62 + double newCost = cost + (weight == null ? 1.0 : weight.weight(edge));
63 + result.updateVertex(nextVertex, edge, newCost, true);
64 + stack.push(nextVertex);
65 + tangent = true;
66 + break;
67 +
68 + } else if (!finished.contains(nextVertex)) {
69 + // We started the vertex, but did not yet finish it, so
70 + // it must be a back-edge.
71 + result.markEdge(edge, EdgeType.BACK_EDGE);
72 + } else {
73 + // The target has been finished already, so what we have
74 + // here is either a forward-edge or a cross-edge.
75 + result.markEdge(edge, isForwardEdge(result, edge) ?
76 + EdgeType.FORWARD_EDGE : EdgeType.CROSS_EDGE);
77 + }
78 + }
79 +
80 + // If we have not been sent on a tangent search and reached the
81 + // end of the current scan normally, mark the node as finished
82 + // and pop it off the vertex stack.
83 + if (!tangent) {
84 + finished.add(vertex);
85 + stack.pop();
86 + }
87 + }
88 +
89 + // Finally, but the paths on the search result and return.
90 + result.buildPaths();
91 + return result;
92 + }
93 +
94 + /**
95 + * Determines whether the specified edge is a forward edge using the
96 + * accumulated set of parent edges for each vertex.
97 + *
98 + * @param result search result
99 + * @param edge edge to be classified
100 + * @return true if the edge is a forward edge
101 + */
102 + protected boolean isForwardEdge(DefaultResult result, E edge) {
103 + // Follow the parent edges until we hit the edge source vertex
104 + V target = edge.src();
105 + V vertex = edge.dst();
106 + Set<E> parentEdges;
107 + while ((parentEdges = result.parents.get(vertex)) != null) {
108 + for (E parentEdge : parentEdges) {
109 + vertex = parentEdge.src();
110 + if (vertex.equals(target)) {
111 + return true;
112 + }
113 + }
114 + }
115 + return false;
116 + }
117 +
118 + /**
119 + * Graph search result which includes edge classification for building
120 + * a spanning tree.
121 + */
122 + public class SpanningTreeResult extends DefaultResult {
123 +
124 + protected final Map<E, EdgeType> edges = new HashMap<>();
125 +
126 + /**
127 + * Creates a new spanning tree result.
128 + *
129 + * @param src search source
130 + * @param dst optional search destination
131 + */
132 + public SpanningTreeResult(V src, V dst) {
133 + super(src, dst);
134 + }
135 +
136 + /**
137 + * Returns the map of edge type.
138 + *
139 + * @return edge to edge type bindings
140 + */
141 + public Map<E, EdgeType> edges() {
142 + return edges;
143 + }
144 +
145 + /**
146 + * Indicates whether or not the edge has been marked with type.
147 + *
148 + * @param edge edge to test
149 + * @return true if the edge has been marked already
150 + */
151 + boolean isEdgeMarked(E edge) {
152 + return edges.containsKey(edge);
153 + }
154 +
155 + /**
156 + * Marks the edge with the specified type.
157 + *
158 + * @param edge edge to mark
159 + * @param type edge type
160 + */
161 + void markEdge(E edge, EdgeType type) {
162 + edges.put(edge, type);
163 + }
164 +
165 + }
166 +
167 +}
1 +package org.onlab.graph;
2 +
3 +import org.junit.Test;
4 +
5 +import java.util.Set;
6 +
7 +import static org.junit.Assert.assertEquals;
8 +import static org.junit.Assert.assertTrue;
9 +import static org.onlab.graph.DepthFirstSearch.EdgeType;
10 +
11 +/**
12 + * Test of the DFS algorithm.
13 + */
14 +public class DepthFirstSearchTest extends AbstractGraphPathSearchTest {
15 +
16 + @Override
17 + protected DepthFirstSearch<TestVertex, TestEdge> graphSearch() {
18 + return new DepthFirstSearch<>();
19 + }
20 +
21 + @Test
22 + public void defaultGraphTest() {
23 + executeDefaultTest(3, 6, 5.0, 12.0);
24 + executeBroadSearch();
25 + }
26 +
27 + @Test
28 + public void defaultHopCountWeight() {
29 + weight = null;
30 + executeDefaultTest(3, 6, 3.0, 6.0);
31 + executeBroadSearch();
32 + }
33 +
34 + protected void executeDefaultTest(int minLength, int maxLength,
35 + double minCost, double maxCost) {
36 + g = new AdjacencyListsGraph<>(vertices(), edges());
37 + DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
38 +
39 + DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
40 + search.search(g, A, H, weight);
41 + Set<Path<TestVertex, TestEdge>> paths = result.paths();
42 + assertEquals("incorrect path count", 1, paths.size());
43 +
44 + Path path = paths.iterator().next();
45 + System.out.println(path);
46 + assertEquals("incorrect src", A, path.src());
47 + assertEquals("incorrect dst", H, path.dst());
48 +
49 + int l = path.edges().size();
50 + assertTrue("incorrect path length " + l,
51 + minLength <= l && l <= maxLength);
52 + assertTrue("incorrect path cost " + path.cost(),
53 + minCost <= path.cost() && path.cost() <= maxCost);
54 +
55 + System.out.println(result.edges());
56 + printPaths(paths);
57 + }
58 +
59 + public void executeBroadSearch() {
60 + g = new AdjacencyListsGraph<>(vertices(), edges());
61 + DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
62 +
63 + // Perform narrow path search to a specific destination.
64 + DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
65 + search.search(g, A, null, weight);
66 + assertEquals("incorrect paths count", 7, result.paths().size());
67 +
68 + int[] types = new int[]{0, 0, 0, 0};
69 + for (EdgeType t : result.edges().values()) {
70 + types[t.ordinal()] += 1;
71 + }
72 + assertEquals("incorrect tree-edge count", 7,
73 + types[EdgeType.TREE_EDGE.ordinal()]);
74 + assertEquals("incorrect back-edge count", 1,
75 + types[EdgeType.BACK_EDGE.ordinal()]);
76 + assertEquals("incorrect cross-edge & forward-edge count", 4,
77 + types[EdgeType.FORWARD_EDGE.ordinal()] +
78 + types[EdgeType.CROSS_EDGE.ordinal()]);
79 + }
80 +
81 +}