DFS is a graph traversal algorithm that starts at a given vertex (or node) and explores as far as possible along each branch before backtracking. It uses a stack data structure (either explicitly or implicitly through recursion) to keep track of the nodes to visit.
// Define a graph using an adjacency list
type Graph = { [key: number]: number[] };
function recursiveDFS(graph: Graph, start: number, visited: Set<number> = new Set()): void {
if (visited.has(start)) {
return;
}
console.log(start);
visited.add(start);
const neighbors = graph[start];
for (const neighbor of neighbors) {
recursiveDFS(graph, neighbor, visited);
}
}
// Example graph
const graph: Graph = {
0: [1, 2],
1: [0, 3],
2: [0, 3],
3: [1, 2]
};
recursiveDFS(graph, 0);
function iterativeDFS(graph: Graph, start: number): void {
const visited = new Set<number>();
const stack: number[] = [start];
while (stack.length > 0) {
const current = stack.pop()!;
if (visited.has(current)) {
continue;
}
console.log(current);
visited.add(current);
const neighbors = graph[current];
for (let i = neighbors.length - 1; i >= 0; i--) {
if (!visited.has(neighbors[i])) {
stack.push(neighbors[i]);
}
}
}
}
iterativeDFS(graph, 0);
DFS can be used to find a specific node in a graph. You can modify the DFS functions to return the node when it is found.
function searchDFS(graph: Graph, start: number, target: number): boolean {
const visited = new Set<number>();
const stack: number[] = [start];
while (stack.length > 0) {
const current = stack.pop()!;
if (visited.has(current)) {
continue;
}
if (current === target) {
return true;
}
visited.add(current);
const neighbors = graph[current];
for (let i = neighbors.length - 1; i >= 0; i--) {
if (!visited.has(neighbors[i])) {
stack.push(neighbors[i]);
}
}
}
return false;
}
const found = searchDFS(graph, 0, 3);
console.log(found);
DFS can also be used to detect cycles in a graph. By keeping track of the nodes in the current path, if we encounter a node that is already in the path, then there is a cycle.
In both recursive and iterative DFS, using a Set
to keep track of visited nodes is a common practice. It provides efficient lookups (O(1) on average) to check if a node has been visited.
If the graph is disconnected, you need to run DFS from each unvisited node to ensure that all nodes are traversed.
function dfsDisconnectedGraph(graph: Graph): void {
const visited = new Set<number>();
for (const node in graph) {
const numNode = parseInt(node);
if (!visited.has(numNode)) {
recursiveDFS(graph, numNode, visited);
}
}
}
dfsDisconnectedGraph(graph);
When working with graphs, it’s important to handle cases where the input graph is not valid, such as having nodes with undefined neighbors.
function isValidGraph(graph: Graph): boolean {
for (const node in graph) {
const neighbors = graph[parseInt(node)];
for (const neighbor of neighbors) {
if (!graph.hasOwnProperty(neighbor)) {
return false;
}
}
}
return true;
}
if (isValidGraph(graph)) {
recursiveDFS(graph, 0);
} else {
console.log('Invalid graph');
}
Use descriptive variable names and add comments to your code to make it more understandable. Also, modularize your code by separating different functions for different tasks, such as the main DFS function, the search function, etc.
DFS is a powerful algorithm for traversing and searching graphs. TypeScript provides a great environment to implement DFS algorithms with its strong typing and object - oriented features. By understanding the fundamental concepts, usage methods, common practices, and best practices, you can efficiently use DFS in your TypeScript projects. Whether you are searching for a specific node, detecting cycles, or just exploring a graph, DFS is a valuable tool in your programming arsenal.