A2SV DFS Lecture - No Code
A2SV DFS Lecture - No Code
2
Prerequisites
- Recursion
- Graph
- Stack
3
Objectives
- Learn about DFS graph traversal
4
Definition
5
Definition
● The algorithm starts at a particular node, known as the source or starting node, and
explores as far as possible along each branch before backtracking.
SOMETHIN
G
6
Implementation`
# base case
visited.add(vertex)
dfs(neighbour, visited)
7
When to Use
● Detecting Cycles
● Path Finding
● Maze Solving
● Solving Puzzles
● Generating Permutations
● Topological Sorting
8
Recursive and Iterative Approach of Implementing
DFS
9
Find if Path
Exists in Graph
10
Recursive Approach
11
Recursive Implementation
● Since it’s a recursive
implementation we have to
obey the 3 rules of recursive
functions.
12
Rule 1: State
● We need to know what node def dfs(node, visited):
we are on.
13
Rule 2: Base case
● For the base case, when if node == destination:
should we stop the recursion return True
when the current node is our
target.
14
Rule 3: Recurrence relation
● We want to traverse all the for neighbour in graph[node]:
adjacent nodes. found = dfs(neighbour)
if found:
return True
15
Build Graph
graph = defaultdict(list)
return dfs(source)
16
Graph traversal
def dfs(node):
if node == destination:
return True
if found:
return True
return False
17
What’s wrong with the above code?
18
Build Graph
graph = defaultdict(list)
visited = set()
return dfs(source, visited)
19
Graph traversal
visited.add(node)
21
Visualization
22
Visualization
23
Visualization
24
Visualization
25
Visualization
26
Visualization
27
Visualization
28
Visualization
29
Visualization
30
Time Complexity Space Complexity
● We will only visit a node once and ● We will store the nodes
edge once
Space Complexity = O (V)
Time Complexity = O (V + E)
- Why not O(V + E)?
- Note: if the graph is a complete - Does it matter if the graph is
graph, the time complexity is complete or not?
= O(V + (V * (V - 1)))
= O(V2)
31
Iterative Approach
32
“The Iterative implementation is just the recursive implementation done
iteratively.”
- Mahatma Gandhi
33
Rule 1: State
stack = [source]
● Since we don’t have access
to the call stack we need our visited = set([source])
own stack to keep track of
the current state.
34
Rule 2: Base case
● The base case is similar to the if node == destination:
recursive implementation.
return True
35
Rule 3: Iteration relation
36
We will continue to run the loop until we reach the base case or until we
visit all the nodes
37
Implementation
Class Solution:
def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
graph = defaultdict(list)
stack = [source]
visited = set([source])
while stack:
node = stack.pop()
if node == destination:
return True
38
return False
DFS on grid
39
DFS on grid
● Grid vertices are cells, and edges connect adjacent ones.
40
Direction vectors
41
Code
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
visited[row][col] = True
43
DFS Applications
44
Path Finding
45
Path Finding
● It is possible that DFS will find a longer path before finding the shortest
one.
● While DFS can be used for pathfinding, it may not always be the most
efficient or accurate method.
46
Pair Programming
47
Check if There is a
Valid Path in a Grid
48
Implementation
def hasValidPath(self, grid): def dfs(row, col):
destination = (len(grid)-1, len(grid[0]) - if (row, col) == destination:
1) return True
directions =
{1: [(0,-1),(0,1)], for row_change, col_change in directions[grid[row][col]]:
2: [(-1,0),(1,0)],
new_row= row + row_change
3: [(0,-1),(1,0)],
new_col = col + col_change
4: [(0,1),(1,0)],
5: [(0,-1),(-1,0)],
if (inbound(new_row, new_col) and
6: [(0,1),(-1,0)]}
(new_row, new_col) not in visited and
return False 49
Determine if a graph is bipartite or not?
50
Bipartite Graph
● Bipartite graphs have
two sets of nodes.
51
Is this graph Bipartite?
52
Is this graph bipartite?
53
How do we identify if a graph is bipartite or not using
dfs?
54
We use DFS Coloring
55
DFS Coloring
● Assign a color to each vertex of a graph in such a way that no two adjacent
vertices have the same color.
56
Is a graph bipartite?
57
Practice Problem
58
Implementation
return result
59
def dfs(node, graph):
for neighbour in graph[node]:
temp = True
if color[neighbour] == -1:
if color[node] == 0:
color[neighbour] = 1
else:
color[neighbour] = 0
temp = temp and dfs(neighbour, graph)
else:
return color[node] != color[neighbour]
return temp
60
Connected components
The connected parts of a graph are called its components
61
Finding Connected Components
62
Brainstorm on how to find connected components.
63
Number of Islands
64
Here's how we can use DFS to find the number of islands
65
1. Initialize all vertices as unvisited.
66
2. For each unvisited vertex, perform a DFS starting from that vertex
67
3. Mark all visited vertices as part of the same connected component as the
starting vertex.
68
4. Repeat steps 2-3 for any remaining unvisited vertices until all vertices have
been visited.
69
After this process, the set of marked vertices for each DFS traversal will give you the
connected components of the graph.
70
Visualization
71
Implementation
def numIslands(grid):
rows = len(grid)
cols = len(grid[0])
islands = 0
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
islands += 1
dfs(i, j)
72
return islands
How can we detect cycles in directed
graph using dfs?
73
Cycle detection
● We will run a series of DFS in the graph.
● From each unvisited (white) vertex, start the DFS, mark it gray (1) while
entering and mark it black (2) on exit
74
DFS Algorithm
● During traversal:
○ Only traverse to white nodes.
○ If a black node is found, skip it - it has been processed.
○ If a grey node is found, this means there is a cycle. Why?
Visualization
94
Cycle detection
95
Practice problem
96
Common Pitfalls
Infinite loops: DFS can get stuck in an infinite loop if it encounters a cycle in the
graph. To avoid this, it is important to keep track of visited nodes and avoid revisiting
them.
97
Common Pitfalls
Stack overflow & Exceeding Maximum Recursion Depth
- DFS uses a stack to keep track of nodes to visit. If the graph is very deep or has a
large number of branches, the stack can become very large and cause a stack
overflow.
- If you are using Python you are aware that the maximum recursion depth is
around 1000, in some cases we might have more than 1000 nodes in our call
stack in these cases we might be faced by maximum recursion depth
exceeded error
98
Common Pitfalls
● To fix the maximum recursion import threading
from sys import stdin,stdout,setrecursionlimit
depth exceeded error we can
from collections import defaultdict
manually increase the recursion
limit.
setrecursionlimit(1 << 30)
threading.stack_size(1 << 27)
100
Recap
101
Recap Points
● DFS Definition and Algorithm
● Visual: summary of DFS algorithm on a graph
● DFS Applications
102
Resources
GeeksForGeeks
Visualization
103
Practice Problems
Employee-importance ✔ Surrounded-regions ✔
Number-of-provinces ✔ Minesweeper ✔
Sum-of-nodes-with-even-valued-grandparent ✔ Lowest-common-ancestor-of-deepest-leaves ✔
Max-area-of-island ✔ Recover-binary-search-tree ✔
Evaluate-division ✔
Sum-root-to-leaf-numbers ✔
Detonate-the-maximum-bombs ✔
104
Quote of the day
- Maori Proverb
105