Graph

Intro

Graphs model relationships between entities using vertices (nodes) and edges. Unlike trees, graphs can have cycles, multiple paths between nodes, and no single root. In .NET, there is no built-in Graph<T> type — you compose graphs from Dictionary<TNode, List<TNode>> for adjacency lists, bool[,] for adjacency matrices, and PriorityQueue<TElement, int> for weighted shortest-path algorithms. A production example: a microservice dependency graph with 200 services uses BFS from a failing service node to identify all downstream services affected by the outage, generating an automated blast radius report in under 50 ms.

Deeper Explanation

The most common representation is an adjacency list: each node maps to its neighbors. Adjacency lists cost O(V + E) space and are efficient for sparse graphs where E ≪ V².

Adjacency matrix costs O(V²) space and offers O(1) edge-existence checks, but wastes memory on sparse graphs. In .NET, a bool[,] or int[,] can serve as a matrix.

Weighted graphs extend either representation: an adjacency list becomes Dictionary<int, List<(int neighbor, int weight)>>, while a matrix stores int[,] with a sentinel (0 or int.MaxValue) for absent edges. Dijkstra's algorithm requires weights and pairs naturally with PriorityQueue<TElement, int> in .NET for O((V + E) log V) performance.

Directed vs undirected: Directed graphs (digraphs) model one-way relationships — A → B does not imply B → A — and require asymmetric adjacency structures. Undirected graphs store each edge in both directions. Most shortest-path algorithms (Dijkstra, Bellman-Ford) operate on directed graphs; connectivity checks work on either.

Structure

graph TD
    A[node a] --> B[node b]
    A --> C[node c]
    B --> D[node d]
    C --> D

Example

var graph = new Dictionary<string, List<string>>
{
    ["A"] = new() { "B", "C" },
    ["B"] = new() { "D" },
    ["C"] = new(),
    ["D"] = new()
};

Pitfalls

Tradeoffs

Questions


Whats next