Graphs
Represent relationships between entities — social networks, maps, dependencies — with BFS, DFS, and adjacency lists.
Why Graphs?
Graphs model relationships. Social networks (who follows whom), maps (roads between cities), dependency trees (build order), web pages (links) — all graphs. If your problem involves connections between things, you’re dealing with a graph.
Real-World Analogy
Like an airline route map — cities are nodes, flight routes are edges. Some routes are direct, some require layovers. Finding the cheapest path from New York to Tokyo is a classic graph problem.
Representing Graphs
// Adjacency list — most common, space-efficient
type Graph = Map<string, string[]>;
function buildGraph(edges: [string, string][]): Graph {
const graph: Graph = new Map();
for (const [from, to] of edges) {
if (!graph.has(from)) graph.set(from, []);
if (!graph.has(to)) graph.set(to, []);
graph.get(from)!.push(to);
graph.get(to)!.push(from); // omit for directed graph
}
return graph;
} DFS and BFS on Graphs
// DFS — go deep, then backtrack
function dfs(graph: Graph, start: string): string[] {
const visited = new Set<string>();
const result: string[] = [];
function visit(node: string) {
if (visited.has(node)) return;
visited.add(node);
result.push(node);
for (const neighbor of graph.get(node) || []) {
visit(neighbor);
}
}
visit(start);
return result;
}
// BFS — explore level by level
function bfs(graph: Graph, start: string): string[] {
const visited = new Set<string>([start]);
const queue: string[] = [start];
const result: string[] = [];
while (queue.length) {
const node = queue.shift()!;
result.push(node);
for (const neighbor of graph.get(node) || []) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
return result;
} Topological Sort
Order nodes so that every directed edge goes from earlier to later. Used for build systems, course prerequisites, task scheduling.
function topologicalSort(graph: Map<string, string[]>): string[] {
const visited = new Set<string>();
const result: string[] = [];
function dfs(node: string) {
if (visited.has(node)) return;
visited.add(node);
for (const neighbor of graph.get(node) || []) {
dfs(neighbor);
}
result.push(node); // add after all dependencies
}
for (const node of graph.keys()) {
dfs(node);
}
return result.reverse();
} BFS vs DFS: Use BFS when you need shortest path (unweighted) or level-by-level exploration. Use DFS when you need to explore all paths, detect cycles, or do topological sorting.
Key Takeaways
- Adjacency lists are the go-to representation — space-efficient and easy to traverse
- Always track visited nodes to avoid infinite loops in graphs with cycles
- BFS finds shortest paths in unweighted graphs; DFS explores all paths
- Topological sort is essential for dependency resolution