Skip to content

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

  1. Adjacency lists are the go-to representation — space-efficient and easy to traverse
  2. Always track visited nodes to avoid infinite loops in graphs with cycles
  3. BFS finds shortest paths in unweighted graphs; DFS explores all paths
  4. Topological sort is essential for dependency resolution