Graph traversal is the process of visiting (checking and/or updating) each vertex in a graph. DFS is one of the main algorithms for graph traversal.
In DFS, we explore as far as possible along each branch before backtracking, which makes it useful for problems like finding paths, connected components, and more.
Here's an implementation of finding connected components in an undirected graph using DFS:
Here's an implementation of finding a path between two vertices in a graph using DFS:
O(V + E), where V is the number of vertices and E is the number of edges in the graph.
O(V), where V is the number of vertices in the graph, for storing the visited set and the recursion stack.
A common application of graph traversal is the Number of Islands problem, where we need to count the number of islands in a 2D grid.
Learn how to use DFS to explore all vertices and edges in a graph, useful for finding paths, connected components, and more.
Graph traversal is the process of visiting (checking and/or updating) each vertex in a graph. DFS is one of the main algorithms for graph traversal.
In DFS, we explore as far as possible along each branch before backtracking, which makes it useful for problems like finding paths, connected components, and more.
Key Insight: DFS is particularly useful for exploring all possible paths in a graph, finding connected components, and solving problems that require exhaustive search.
Identifying groups of vertices that are connected to each other, useful for analyzing the structure of a graph.
Finding a path between two vertices in a graph, useful for navigation and routing problems.
Finding a path through a maze, where each cell is a vertex and adjacent cells are connected by edges.
Counting the number of islands in a 2D grid, where each land cell is a vertex and adjacent land cells are connected by edges.
Initialize a visited set to keep track of visited vertices. For specific applications, initialize additional data structures as needed.
For each unvisited vertex, start DFS from it. This handles disconnected graphs and ensures all vertices are visited.
Mark the current vertex as visited, process it according to the specific problem, and recursively visit all its unvisited neighbors.
Here's an implementation of finding connected components in an undirected graph using DFS:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354function findConnectedComponents(graph) { // Set to keep track of visited vertices const visited = new Set(); // Array to store all connected components const components = []; // For each vertex in the graph for (const vertex in graph) { // If this vertex has not been visited yet if (!visited.has(vertex)) { // Find all vertices in the same component as this vertex const component = []; dfs(graph, vertex, visited, component); // Add this component to the list of components components.push(component); } } return components;} function dfs(graph, vertex, visited, component) { // Mark the vertex as visited visited.add(vertex); // Add the vertex to the current component component.push(vertex); // Visit all neighbors for (const neighbor of graph[vertex]) { if (!visited.has(neighbor)) { dfs(graph, neighbor, visited, component); } }} // Example usage:const undirectedGraph = { 'A': ['B', 'C'], 'B': ['A', 'D', 'E'], 'C': ['A', 'F'], 'D': ['B'], 'E': ['B'], 'F': ['C'], 'G': ['H'], 'H': ['G'], 'I': []}; const components = findConnectedComponents(undirectedGraph);console.log(components);// Output: [['A', 'B', 'D', 'E', 'C', 'F'], ['G', 'H'], ['I']]
How it works: We iterate through each vertex in the graph. If a vertex hasn't been visited yet, we start DFS from it to find all vertices in its connected component. Each DFS call explores all vertices reachable from the starting vertex, effectively identifying a connected component.
Here's an implementation of finding a path between two vertices in a graph using DFS:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455function findPath(graph, start, end) { // Set to keep track of visited vertices const visited = new Set(); // Array to store the path const path = []; // Find a path from start to end const pathExists = dfs(graph, start, end, visited, path); // If a path exists, return it; otherwise, return an empty array return pathExists ? path : [];} function dfs(graph, current, end, visited, path) { // Mark the current vertex as visited visited.add(current); // Add the current vertex to the path path.push(current); // If we've reached the end, we've found a path if (current === end) { return true; } // Visit all neighbors for (const neighbor of graph[current]) { if (!visited.has(neighbor)) { // If a path is found through this neighbor, return true if (dfs(graph, neighbor, end, visited, path)) { return true; } } } // If no path is found through this vertex, remove it from the path path.pop(); // No path found return false;} // Example usage:const graph = { 'A': ['B', 'C'], 'B': ['A', 'D', 'E'], 'C': ['A', 'F'], 'D': ['B'], 'E': ['B', 'F'], 'F': ['C', 'E']}; const path = findPath(graph, 'A', 'F');console.log(path); // Output: ['A', 'C', 'F']
How it works: We start DFS from the start vertex and try to reach the end vertex. We keep track of the path as we go. If we reach the end vertex, we've found a path. If we explore all possibilities without finding a path, we return an empty array.
O(V + E), where V is the number of vertices and E is the number of edges in the graph.
Explanation: We visit each vertex once (O(V)) and explore each edge once (O(E)). Even though we run DFS multiple times, each vertex and edge is processed exactly once across all DFS calls.
O(V), where V is the number of vertices in the graph.
Explanation: We need to store the visited set (O(V)) and the recursion stack (O(V) in the worst case). Additionally, for specific applications like finding connected components or paths, we need O(V) space for storing the results. Overall, the space complexity is O(V).
A common application of graph traversal is the Number of Islands problem, where we need to count the number of islands in a 2D grid. An island is a group of connected land cells (represented by '1'), surrounded by water cells (represented by '0').
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556function numIslands(grid) { if (!grid || grid.length === 0) { return 0; } const rows = grid.length; const cols = grid[0].length; let count = 0; // Function to check if a cell is valid and is land function isValid(row, col) { return row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] === '1'; } // DFS to mark all connected land cells as visited function dfs(row, col) { // Mark the current cell as visited by changing '1' to '0' grid[row][col] = '0'; // Check all four directions: up, right, down, left const directions = [[-1, 0], [0, 1], [1, 0], [0, -1]]; for (const [dr, dc] of directions) { const newRow = row + dr; const newCol = col + dc; if (isValid(newRow, newCol)) { dfs(newRow, newCol); } } } // Iterate through each cell in the grid for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { // If we find a land cell, start DFS and increment count if (grid[i][j] === '1') { count++; dfs(i, j); } } } return count;} // Example usage:const grid = [ ['1', '1', '0', '0', '0'], ['1', '1', '0', '0', '0'], ['0', '0', '1', '0', '0'], ['0', '0', '0', '1', '1']]; const islands = numIslands(grid);console.log(islands); // Output: 3
How it works: We iterate through each cell in the grid. When we find a land cell ('1'), we start DFS from that cell to mark all connected land cells as visited (by changing them to '0'). Each time we start a new DFS, we've found a new island, so we increment our count.
Graph traversal is the process of visiting (checking and/or updating) each vertex in a graph. DFS is one of the main algorithms for graph traversal.
In DFS, we explore as far as possible along each branch before backtracking, which makes it useful for problems like finding paths, connected components, and more.
Here's an implementation of finding connected components in an undirected graph using DFS:
Here's an implementation of finding a path between two vertices in a graph using DFS:
O(V + E), where V is the number of vertices and E is the number of edges in the graph.
O(V), where V is the number of vertices in the graph, for storing the visited set and the recursion stack.
A common application of graph traversal is the Number of Islands problem, where we need to count the number of islands in a 2D grid.
Learn how to use DFS to explore all vertices and edges in a graph, useful for finding paths, connected components, and more.
Graph traversal is the process of visiting (checking and/or updating) each vertex in a graph. DFS is one of the main algorithms for graph traversal.
In DFS, we explore as far as possible along each branch before backtracking, which makes it useful for problems like finding paths, connected components, and more.
Key Insight: DFS is particularly useful for exploring all possible paths in a graph, finding connected components, and solving problems that require exhaustive search.
Identifying groups of vertices that are connected to each other, useful for analyzing the structure of a graph.
Finding a path between two vertices in a graph, useful for navigation and routing problems.
Finding a path through a maze, where each cell is a vertex and adjacent cells are connected by edges.
Counting the number of islands in a 2D grid, where each land cell is a vertex and adjacent land cells are connected by edges.
Initialize a visited set to keep track of visited vertices. For specific applications, initialize additional data structures as needed.
For each unvisited vertex, start DFS from it. This handles disconnected graphs and ensures all vertices are visited.
Mark the current vertex as visited, process it according to the specific problem, and recursively visit all its unvisited neighbors.
Here's an implementation of finding connected components in an undirected graph using DFS:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354function findConnectedComponents(graph) { // Set to keep track of visited vertices const visited = new Set(); // Array to store all connected components const components = []; // For each vertex in the graph for (const vertex in graph) { // If this vertex has not been visited yet if (!visited.has(vertex)) { // Find all vertices in the same component as this vertex const component = []; dfs(graph, vertex, visited, component); // Add this component to the list of components components.push(component); } } return components;} function dfs(graph, vertex, visited, component) { // Mark the vertex as visited visited.add(vertex); // Add the vertex to the current component component.push(vertex); // Visit all neighbors for (const neighbor of graph[vertex]) { if (!visited.has(neighbor)) { dfs(graph, neighbor, visited, component); } }} // Example usage:const undirectedGraph = { 'A': ['B', 'C'], 'B': ['A', 'D', 'E'], 'C': ['A', 'F'], 'D': ['B'], 'E': ['B'], 'F': ['C'], 'G': ['H'], 'H': ['G'], 'I': []}; const components = findConnectedComponents(undirectedGraph);console.log(components);// Output: [['A', 'B', 'D', 'E', 'C', 'F'], ['G', 'H'], ['I']]
How it works: We iterate through each vertex in the graph. If a vertex hasn't been visited yet, we start DFS from it to find all vertices in its connected component. Each DFS call explores all vertices reachable from the starting vertex, effectively identifying a connected component.
Here's an implementation of finding a path between two vertices in a graph using DFS:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455function findPath(graph, start, end) { // Set to keep track of visited vertices const visited = new Set(); // Array to store the path const path = []; // Find a path from start to end const pathExists = dfs(graph, start, end, visited, path); // If a path exists, return it; otherwise, return an empty array return pathExists ? path : [];} function dfs(graph, current, end, visited, path) { // Mark the current vertex as visited visited.add(current); // Add the current vertex to the path path.push(current); // If we've reached the end, we've found a path if (current === end) { return true; } // Visit all neighbors for (const neighbor of graph[current]) { if (!visited.has(neighbor)) { // If a path is found through this neighbor, return true if (dfs(graph, neighbor, end, visited, path)) { return true; } } } // If no path is found through this vertex, remove it from the path path.pop(); // No path found return false;} // Example usage:const graph = { 'A': ['B', 'C'], 'B': ['A', 'D', 'E'], 'C': ['A', 'F'], 'D': ['B'], 'E': ['B', 'F'], 'F': ['C', 'E']}; const path = findPath(graph, 'A', 'F');console.log(path); // Output: ['A', 'C', 'F']
How it works: We start DFS from the start vertex and try to reach the end vertex. We keep track of the path as we go. If we reach the end vertex, we've found a path. If we explore all possibilities without finding a path, we return an empty array.
O(V + E), where V is the number of vertices and E is the number of edges in the graph.
Explanation: We visit each vertex once (O(V)) and explore each edge once (O(E)). Even though we run DFS multiple times, each vertex and edge is processed exactly once across all DFS calls.
O(V), where V is the number of vertices in the graph.
Explanation: We need to store the visited set (O(V)) and the recursion stack (O(V) in the worst case). Additionally, for specific applications like finding connected components or paths, we need O(V) space for storing the results. Overall, the space complexity is O(V).
A common application of graph traversal is the Number of Islands problem, where we need to count the number of islands in a 2D grid. An island is a group of connected land cells (represented by '1'), surrounded by water cells (represented by '0').
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556function numIslands(grid) { if (!grid || grid.length === 0) { return 0; } const rows = grid.length; const cols = grid[0].length; let count = 0; // Function to check if a cell is valid and is land function isValid(row, col) { return row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] === '1'; } // DFS to mark all connected land cells as visited function dfs(row, col) { // Mark the current cell as visited by changing '1' to '0' grid[row][col] = '0'; // Check all four directions: up, right, down, left const directions = [[-1, 0], [0, 1], [1, 0], [0, -1]]; for (const [dr, dc] of directions) { const newRow = row + dr; const newCol = col + dc; if (isValid(newRow, newCol)) { dfs(newRow, newCol); } } } // Iterate through each cell in the grid for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { // If we find a land cell, start DFS and increment count if (grid[i][j] === '1') { count++; dfs(i, j); } } } return count;} // Example usage:const grid = [ ['1', '1', '0', '0', '0'], ['1', '1', '0', '0', '0'], ['0', '0', '1', '0', '0'], ['0', '0', '0', '1', '1']]; const islands = numIslands(grid);console.log(islands); // Output: 3
How it works: We iterate through each cell in the grid. When we find a land cell ('1'), we start DFS from that cell to mark all connected land cells as visited (by changing them to '0'). Each time we start a new DFS, we've found a new island, so we increment our count.