Depth First Seach: Output Fix bool findPath (Node* currNode, int destID, list<Edge> path) { if (currNode->id == destID) return true; currNode->visited = true; for each (outEdge of currNode) { Node *toNode = outEdge.toNode; if (!toNode->visited) { list<Edge> newPath = path; newPath.push_back (outEdge); bool found = findPath (toNode, destID, newPath); if (found) return (true); } return (false); Anything Missing? {a,b} {a,b,c} a b c d {} {a,b,c,d} {a} dest source
Easy Fix Part 2 dest source bool findPath (Node* currNode, int destID, list<Edge> path) { if (currNode->id == destID) print out or save the path, then return (true); currNode->visited = true; for each (outEdge of currNode) { Node *toNode = outEdge.toNode; if (!toNode->visited) { list<Edge> newPath = path; newPath.push_back (outEdge); bool found = findPath (toNode, destID, newPath); if (found) return (true); } return (false); dest source
Depth First Search (DFS) Developed depth first search in a graph Need a visited flag Unlike a tree (can go in circles otherwise) Graph is big (>100,000 nodes, N) Complexity of DFS?
Complexity Analysis bool findPath (Node* currNode, int destID, list<Edge> path) { if (currNode->id == destID) print out or save the path, then return (true); currNode->visited = true; for each (outEdge of currNode) { Node *toNode = outEdge.toNode; if (!toNode->visited) { list<Edge> newPath = path; newPath.push_back (outEdge); bool found = findPath (toNode, destID, newPath); if (found) return (true); } return (false); At most one findPath call per Node Executes once per outgoing edge Average: about 4
Complexity findPath executes at most O(N) times Constant, O(1), work in each call Except copying path path could have O(N) nodes Total worst-case complexity: O(N2) Average path shorter Average case somewhat better, but hard to analyze
Complexity Can we do better? Don’t pass entire path around One way: each Node stores the edge used to reach it e.g. node.reachingEdge Reconstruct path when you reach the dest By following the previous edges / “bread crumbs” Now work per findPath is O(1) Total complexity is O(N) Good for a graph algorithm!
DFS - Demo
Path Quality We’re finding a path, not the shortest path dest source We’re finding a path, not the shortest path Circuitous routes / directions! Low marks! How to fix?
Shortest Path Algorithm #2 Depth First Search with Re-Exansion Path Enumeration Shortest Path Algorithm #2 Depth First Search with Re-Exansion
Don’t Stop at First Path Found? bool findPath (Node* currNode, int destID, float pLen) { pLen = updatePathLength (pLen, currNode); if (currNode->id == destID) if (pLen < bestPathLen) { ...// Update bestPathLen and save best path (globals) } return (true); currNode->visited = true; for each (outEdge of currNode) { Node *toNode = outEdge.toNode; if (!toNode->visited) { bool found = findPath ( toNode, destID); . . . 4 4 3 3 5 1 dest 2 source
More Complex Test Case 6 7 2 3 1 dest source 6 4 8 5 This node already marked visited won’t call findPath on it again Blocks us from ever finding best path What can we do?
Let a Node be Explored Multiple Times? bool findPath (Node* currNode, int destID, float pLen) { . . . // Check if we’ve reached the destination // and if this is the best path found so far currNode->visited = true; for each (outEdge of currNode) { Node *toNode = outEdge.toNode; if (!toNode->visited) { bool found = findPath (toNode, destID); if (found) { currNode->visited = false; // Can be explored again return (true); } currNode->visited = false; return (false);
Testing DFS with Re-expansion 2 3 6 7 1 dest source 4 8 6 5
Testing DFS with Re-expansion 2 3 4 5 1 dest source 4 5 6
Testing DFS with Re-expansion 2 3 4 5 1 dest source 2 3 4 6 Not blocked by previously visited nodes! But still avoids infinite loops! Explored all paths to find best one
Computational Complexity? dest source Solve with recurrence Graph above: N-1 nodes T(N-1) to find best path
Computational Complexity? dest source New graph has 1 more node: N nodes T(N) = 2 * T(N-1) (why?) T(N) = 2 * 2 * T(N-2) T(N) = 2 * 2 * 2 * T(N-3) Iterate until we get to T(0) = c T(N) = 2N c O(2N)
Algorithm changes big quality and runtime impact! Fast Enough? N 100,000 Say one call to findPath takes 300 clock cycles @ 3 GHz 10-7 s DFS with re-expansion (path enumeration) O(2N) 2100,000 10-7 s ~1010,000 years! (Age of the universe ~1010 years) Regular DFS (never explore once visited)? Poor implementation (store / copy whole path) O(N2) 100,0002 10-7 s 1000 s Good implementation O(N) 100,000 10-7 s Less than 1 s! Algorithm changes big quality and runtime impact!