Showing
2 changed files
with
248 additions
and
0 deletions
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 | +} |
-
Please register or login to post a comment