Warmup

Warmup CSE 373: Graph traversal Given a graph, assign each node one of two colors such that no two adjacent vertices have the same color. (If it’s impossible to color the graph this way, your should say so). Michael Lee Solution: This algorithm is known as the 2-color algorithm. We Friday, Feb 16, 2018 can solve it by using any graph traversal algorithm, and alternating colors as we go from node to node.

1 2

Goal: How do we traverse graphs? Breadth-first search (BFS) example

Today’s goal: how do we traverse graphs? search(v): Idea 1: Just get a list of the vertices and loop over them visited = empty set f queue.enqueue(v) Problem: What if we want to traverse graphs following the edges? visited.add(v) g d a j For example, can we... while (queue is not empty): i curr = queue.dequeue() h I Traverse a graph to find if there’s a connection from one node for (w : v.neighbors()): e b to another? if (w not in visited): queue.enqueue(w) I Determine if we can start from our node and touch every visited.add(curr) c other node? Current node: a b d c e f g h i I Find the shortest path between two nodes? Queue: a, b, d, c, e, f, g, h, i, Solution: Use graph traversal like breadth-first search Visited: a, b, d, c, e, f, g, h, i, and depth-first search

3 4

Breadth-first search (BFS) Breadth-first search (BFS)

Breadth-first traversal, core idea:

1. Use something (e.g. a queue) to keep track of every vertex to Pseudocode: visit search(v): visited = empty set 2. Add and remove nodes from queue until it’s empty queue.enqueue(v) 3. Use a set to store nodes we don’t want to recheck/revisit visited.add(v)

4. Runtime: while (queue is not empty): curr = queue.dequeue() I We visit each node once. I For each node, check each edge to see if we should add to for (w : v.neighbors()): if (w not in visited): queue queue.enqueue(w) I So we check each edge at most twice visited.add(curr) So, O (|V | + 2|E|), which simplifies to O (|V | + |E|).

5 6 An interesting property... An interesting property...

Note: We visited the nodes in “rings” – maintained a gradually What does this look like for trees? growing “frontier” of nodes.

f

g d a j i h e b

c The algorithm traverses the width, or “breadth” of the

7 8

Depth-first search (DFS) Depth-first search (DFS) example

Question: Why a queue? Can we use other data structures? search(v): visited = empty set f Answer: Yes! Any kind of list-like thing that supports appends stack.push(v) and removes works! For example, what if we try using a stack? while (stack is not empty): g curr = stack.pop() d The BFS algorithm: The DFS algorithm: visited.add(curr) i a j search(v): search(v): visited = empty set visited = empty set for (w : v.neighbors()): h if (w not in visited): e stack.push(v) b queue.enqueue(v) visited.add(v) stack.push(w) visited.add(v) while (stack is not empty): c while (queue is not empty): curr = stack.pop() curr = queue.dequeue() visited.add(curr) Current node: adgihfecb for (w : v.neighbors()): for (w : v.neighbors()): Stack: a, b, d, e, f, g, h, i, c, if (w not in visited): if (w not in visited): queue.enqueue(w) stack.push(w) visited.add(curr) visited.add(v) Visited: a, b, d, e, f, g, h, i, e, c,

9 10

Depth-first search (DFS) An interesting property...

Depth-first traversal, core idea: Note: Rather the growing the node in “rings”, we randomly 1. Instead of using a queue, use a stack. Otherwise, keep wandered through the graph until we got stuck, then everything the same. “backtracked”. 2. Runtime: also O (|V | + |E|) for same reasons as BFS f Pseudocode: search(v): g d visited = empty set a j i stack.push(v) visited.add(v) h e b while (stack is not empty): curr = stack.pop() c for (w : v.neighbors()): if (w not in visited): stack.push(w) visited.add(curr) 11 12 An interesting property... Compare and contrast

What does this look like for trees? Question: When do we use BFS vs DFS? Related question: How much memory does BFS and DFS use in the worst case?

I BFS: O (|V |) – what if every node is connected to the start? I DFS: O (|V |) – what if the nodes are arranged like a linked list?

So, in the worst case, BFS and DFS both have the same The algorithm traverses to the bottom first: it prioritizes the worst-case runtime and memory usage. “depth” of the tree They only differ in what order they visit the nodes. Note: rest of algorithm omitted

13 14

Compare and contrast Design challenge

How much memory does BFS and DFS use in the average case? Question: How would you modify BFS to find the shortest path between every node? Related question: how much memory do they use when we want to traverse a tree? x y

I BFS: O (“width” of tree) = O (num leaves) w z I DFS: O (height)

For graphs: S E a b I Use BFS if graph is “narrow”, or if solution is “near” start I Use DFS if graph is “wide” Observation: Since BFS moves out in rings, we will reach the end In practice, graphs are often large/very wide, so DFS is often a node via the path of length 3 first. good default choice. (It’s also possible to implement DFS Idea: when we enqueue, store where we came from in some way. recursively!) (e.g. mark node, use a dictionary...) 15 16 After BFS is done, backtrack.

Design challenge: Design challenge: pathfinding

Question: What if the edges have weights? Question: How would you modify BFS to find the shortest path 2 y between every node? 2 x 2 w z x y 2 2 w z S E

100 a b 100 S E 100

a b Weighted graph A weighted graph is a kind of graph where each edge has a Now, start from any node, follow arrows, then reverse to get path. numerical “weight” associated with it. This number can represent anything, but is often (but not always!) used to indicate the “cost” of traveling down that edge. 17 18 And we’re done! Now, to find the shortest path, from a to a node, And we’re done! start at the end, trace the red arrows backwards, and reverse the Now, to find the shortest path, from a to a node, start at the end, list. trace the red arrows backwards, and reverse the list.

And we’re done! Now, to find the shortest path, from a to a node, start at the end, trace And we’re done! Now, to find the shortest path, from a to a node, the red arrows backwards, and reverse the list. start at the end, trace the red arrows backwards, and reverse the list.

Pathfinding and DFS Dijkstra’s algorithm

Core idea:

We can use BFS to correctly find the shortest path between two 1. Assign each node an initial cost of ∞ nodes in an unweighted graph... 2. Set our starting node’s cost to 0 3. Update all adjacent vertices costs to the minimum known cost 4. Mark the current node as being “done” 5. Pick the next unvisited node with the minimum cost. Go to ...but it fails if the graph is weighted! step 3. We need a better algorithm. Metaphor: Treat edges as canals and edge weights as distance. Imagine opening a dam at the starting node. How long does it take for the water to reach each vertex? Today: Dijkstra’s algorithm Caveat: Dijkstra’s algorithm only guaranteed to work for graphs with no negative edge weights.

19 Pronunciation: DYKE-struh (“dijk” rhymes with “bike”) 20

Dijkstra’s algorithm Dijkstra’s algorithm

Suppose we start at vertex “a”: Suppose we start at vertex “a”:

2 2 3 ∞ ∞ ∞ ∞ a b f h 2 2 3 a b f h 1 10 1 4 5 1 10 2 4 5 1 9 2 1 9 2 11 1 d c e g 2 11 d c e g 3 ∞ ∞ ∞ ∞ 3 7 7 We initially assign all nodes a cost of infinity.

21 21

Dijkstra’s algorithm Dijkstra’s algorithm

Suppose we start at vertex “a”: Suppose we start at vertex “a”:

0 ∞ ∞ ∞ 0 2 ∞ ∞ 2 2 3 2 2 3 a b f h a b f h

1 1 10 10 4 5 1 4 5 1 2 2 9 9 1 1 2 11 2 11 d c e g d c e g ∞ ∞ ∞ ∞ ∞ ∞ 3 4 1 3 7 7 Next, assign the starting node a cost of 0. Next, update all adjacent node costs as well as the backpointers.

21 21 And we’re done! Now, to find the shortest path, from a to a node, start at the end, trace the red arrows backwards, and reverse the And we’re done! Now, to find the shortest path, list. from a to a node, start at the end, trace the red arrows backwards, and reverse the list.

And we’re done! Now, to find the shortest path, from a to a node, start at the end, trace the red arrows backwards, and reverse the list. And we’re done! Now, to find the shortest path, from a to a node, start at the end, trace the red arrows backwards, and reverse the list.

And we’re done! Now, to find the shortest path, from And we’re done! a to a node, start at the end, trace the red arrows backwards, and Now, to find the shortest path, from a to a node, start at the end, reverse the list. trace the red arrows backwards, and reverse the list.

Dijkstra’s algorithm Dijkstra’s algorithm

Suppose we start at vertex “a”: Suppose we start at vertex “a”:

0 2 ∞ ∞ 0 2 ∞ ∞ 2 2 3 2 2 3 a b f h a b f h

1 1 10 10 4 5 1 4 5 1 2 2 9 9 1 1 2 11 2 11 d c e g d c e g ∞ ∞ ∞ 4 1 3 4 1 12 3 7 7 The pending node with the smallest cost is c, so we visit that next. We consider all adjacent nodes. a is fixed, so we only need to update e. Note the new cost of e is the sum of the weights for

21 a − c and c − e. 21

Dijkstra’s algorithm Dijkstra’s algorithm

Suppose we start at vertex “a”: Suppose we start at vertex “a”:

0 2 ∞ ∞ 0 2 4 ∞ 2 2 3 2 2 3 a b f h a b f h

1 1 10 10 4 5 1 4 5 1 2 2 9 9 1 1 2 11 2 11 d c e g d c e g ∞ ∞ 4 1 12 3 4 1 12 3 7 7 b is the next pending node with smallest cost. The adjacent nodes are c, e, and f . The only node where we can update the cost is f . Note the route a − b − e has the same cost as

21 a − c − e, so there’s no point in updating the backpointer to e. 21

Dijkstra’s algorithm Dijkstra’s algorithm

Suppose we start at vertex “a”: Suppose we start at vertex “a”:

0 2 4 ∞ 0 2 4 ∞ 2 2 3 2 2 3 a b f h a b f h

1 1 10 10 4 5 1 4 5 1 2 2 9 9 1 1 2 11 2 11 d c e g d c e g ∞ ∞ 4 1 12 3 4 1 12 3 7 7 Both d and f have the same cost, so let’s (arbitrarily) pick d next. Next up is f . Note that we can’t adjust any of our neighbors.

21 21 And we’re done! Now, to find the And we’re done! Now, to find the shortest path, from a to a node, start at the end, trace the red shortest path, from a to a node, start at the end, trace the red arrows backwards, and reverse the list. arrows backwards, and reverse the list.

And we’re done! Now, to find the shortest path, And we’re done! Now, to find the shortest path, from from a to a node, start at the end, trace the red arrows backwards, a to a node, start at the end, trace the red arrows backwards, and and reverse the list. reverse the list.

And we’re done! Now, to find the shortest path, from a to a node, start at the end, trace the red And we’re done! Now, to find the shortest path, from arrows backwards, and reverse the list. a to a node, start at the end, trace the red arrows backwards, and reverse the list.

Dijkstra’s algorithm Dijkstra’s algorithm

Suppose we start at vertex “a”: Suppose we start at vertex “a”:

0 2 4 7 0 2 4 7 2 2 3 2 2 3 a b f h a b f h

1 1 10 10 4 5 1 4 5 1 2 2 9 9 1 1 2 11 2 11 d c e g d c e g ∞ ∞ 4 1 12 3 4 1 12 3 7 7 The only neighbor we is h. h has the smallest cost now.

21 21

Dijkstra’s algorithm Dijkstra’s algorithm

Suppose we start at vertex “a”: Suppose we start at vertex “a”:

0 2 4 7 0 2 4 7 2 2 3 2 2 3 a b f h a b f h

1 1 10 10 4 5 1 4 5 1 2 2 9 9 1 1 2 11 2 11 d c e g d c e g 4 1 12 3 8 4 1 12 3 8 7 7 We update g. Next up is g.

21 21

Dijkstra’s algorithm Dijkstra’s algorithm

Suppose we start at vertex “a”: Suppose we start at vertex “a”:

0 2 4 7 0 2 4 7 2 2 3 2 2 3 a b f h a b f h

1 1 10 10 4 5 1 4 5 1 2 2 9 9 1 1 2 11 2 11 d c e g d c e g 4 1 11 3 8 4 1 11 3 8 7 7 The two adjacent nodes are f and e. f is fixed so we leave it The last pending node is e. We visit it, and check for any unfixed alone. We however will update e: our current route is cheaper adjacent nodes (there are none).

then the previous route, so we update both the cost and the 21 21 backpointer. Dijkstra’s algorithm Dijkstra’s algorithm

Suppose we start at vertex “a”: Some implementation details... 0 2 4 7 2 2 3 I How do we keep track of the node costs? a b f h I Could use a dictionary 1 I Could manually mark each node 10 4 5 1 I How do we find the node with the smallest cost? 2 9 I Could maintain a sorted list 1 2 11 I Could use a heap! d c e g I If we’re using a heap, how do we update node costs? 4 1 11 3 8 changeKeyPriority(...) 7 I Could add a method to heap I Alternatively, add the node and the cost to the heap again And we’re done! Now, to find the shortest path, from a to a node, (and ignore duplicates) start at the end, trace the red arrows backwards, and reverse the

list. 21 22

Dijkstra’s algorithm

The pseudocode def dijkstra(start): backpointers = empty Dictionary of vertex to vertex costs = Dictionary of vertex to double, initialized to infinity visited = empty Set

heap = new Heap(); heap.put([start, 0]) cost.put(start, 0) while (heap is not empty): current, currentCost = heap.removeMin() skip if visited.contains(current), else visited.add(current)

for (edge : current.getOutEdges()): skip if visited.contains(edge.dest), else visited.add(edge.dest)

newCost = currentCost + edge.cost if (newCost > cost.get(edge.dest)): cost.put(edge.dest, newCost) heap.insert([edge.dest, newCost]) backpointers.put(edge.dest, current)

use backpointers dictionary to get path 23