Skip to main content

DFS & BFS on Graphs

Depth-First Search (DFS) on Graphs

Depth-First Search (DFS) is a fundamental algorithm for traversing or searching through graph data structures. The algorithm starts at a given node and explores as far as possible along each branch before backtracking.

How DFS Works

DFS can be implemented using either a recursive approach or an iterative approach with a stack. The key idea is to start from a source node, visit its adjacent nodes, and continue this process until all nodes reachable from the source are visited.

Pseudocode

Here's the pseudocode for a recursive DFS on an undirected graph:

const graph = {
A: ['B', 'C'],
B: ['A', 'D', 'E'],
C: ['A', 'F'],
D: ['B'],
E: ['B'],
F: ['C'],
G: ['L'],
M: ['S']
};

Recusrive DFS

const dfs = (graph, startNode, visited = new Set()) => {
visited.add(startNode);
console.log(startNode);

for (const neighbour of graph[startNode] || []) {
if (!visited.has(neighbour)) {
dfs(graph, neighbour, visited);
}
}
};

const visited = new Set();

// To handle disconnected components, loop through all nodes
for (const node of Object.keys(graph)) {
if (!visited.has(node)) {
dfs(graph, node, visited);
}
}

Iterative DFS

const dfsIterative = (graph, start, visited = new Set()) => {
const stack = [start]
visited.add(start)

while (stack.length) {
const current = stack.pop()
console.log(current);
for (const neighbour of graph[current]) {
if (!visited.has(neighbour)) {
stack.push(neighbour)
visited.add(neighbour)
}
}

}
}

// To handle disconnected components, loop through all nodes
const allNodes = new Set(Object.keys(graph));
const visited = new Set();

for (const node of allNodes) {
if (!visited.has(node)) {
dfsIterative(graph, node, visited);
}
}

Breadth-First Search (BFS) on Graphs

Breadth-First Search (BFS) is an algorithm used for traversing or searching through graph or tree data structures. The algorithm starts at a given node and explores all of its neighbors at the present depth before moving on to nodes at the next depth level.

How BFS Works

BFS is typically implemented using a queue data structure. It starts from a source node and explores all of its neighbors first before moving on to the next layer of nodes. This ensures that nodes are visited in the order of their distance from the source node.

Pseudocode

Here’s the pseudocode for BFS on an undirected graph:

BFS

const bfs = (graph, start, visited = new Set()) => {
const queue = [start]
visited.add(start)

while (queue.length) {
const current = queue.shift()
console.log(current);
for (const neighbour of graph[current]) {
if (!visited.has(neighbour)) {
queue.push(neighbour)
visited.add(neighbour)
}
}
}
}

// To handle disconnected components, loop through all nodes
const allNodes = new Set(Object.keys(graph));
const visited = new Set();

for (const node of allNodes) {
if (!visited.has(node)) {
bfs(graph, node, visited);
}
}

Shortest Path Using BFS

const bfsShortestPath = (graph, start, end) => {
const queue = [[start]];
const visited = new Set();

while (queue.length > 0) {
const path = queue.shift(); // Get the current path
const node = path[path.length - 1]; // Get the last node in the path

if (node === end) {
return path; // Return the path if we've reached the end
}

if (!visited.has(node)) {
visited.add(node); // Mark the node as visited

for (const neighbor of graph[node] ?? []) {
// Create a new path to the neighbor and enqueue it
const newPath = [...path, neighbor];
queue.push(newPath);
}
}
}

return []; // Return an empty array if there's no path to the end
}

// Example usage with disconnected vertices:
const graph = {
A: ['B', 'C'],
B: ['A', 'D', 'E'],
C: ['A', 'F'],
D: ['B'],
E: ['B', 'F'],
F: ['C', 'E'],
G: [], // Disconnected vertex G
};

const shortestPath1 = bfsShortestPath(graph, 'A', 'F');
console.log(shortestPath1); // Output: ['A', 'C', 'F']

const shortestPath2 = bfsShortestPath(graph, 'A', 'G');
console.log(shortestPath2); // Output: [] (no path to G)

Shortest Path Using BFS for a 2D Grid

const bfsShortestPath = (grid, start, end) => {
const rows = grid.length;
const cols = grid[0].length;
const directions = [[1, 0], [0, 1], [-1, 0], [0, -1]]; // Down, Right, Up, Left
const queue = [[...start, 0]]; // [row, col, distance]
const visited = new Set(); // To track visited cells
visited.add(start.toString()); // Mark the start cell as visited

while (queue.length > 0) {
const [row, col, distance] = queue.shift(); // Dequeue

// Check if we reached the end
if (row === end[0] && col === end[1]) {
return distance; // Return the distance (shortest path length)
}

// Explore the neighbors
for (const [dRow, dCol] of directions) {
const newRow = row + dRow;
const newCol = col + dCol;

// Check if the new position is within bounds and not visited
if (
newRow >= 0 &&
newRow < rows &&
newCol >= 0 &&
newCol < cols &&
grid[newRow][newCol] === 0 && // Assuming 0 is a valid cell
!visited.has([newRow, newCol].toString())
) {
visited.add([newRow, newCol].toString()); // Mark as visited
queue.push([newRow, newCol, distance + 1]); // Enqueue with increased distance
}
}
}

return -1; // Return -1 if there's no path
}

// Example usage:
const grid = [
[0, 0, 0, 1],
[0, 1, 0, 0],
[0, 0, 0, 0],
[1, 0, 1, 0]
];

const start = [0, 0]; // Starting position
const end = [3, 3]; // Ending position

const shortestPathLength = bfsShortestPath(grid, start, end);
console.log(shortestPathLength); // Should output: 6