Graphs
Vertices and edges — the universal abstraction
Graph · algorithms lab
Click any node to set start, then run BFS or DFS. The frontier panel renders as a queue for BFS and a stack for DFS — that single difference is what makes the two visit orders diverge.
click a node, then bfs or dfs
Weighted edges, real shortest-path search. Click a node to set start (green); click another to set goal (red). Dijkstra explores by cost-so-far; A* uses Euclidean distance to the goal as a heuristic and visibly skips dead-end directions.
click two nodes to pick start and goal
A robot on a discrete map. Click cells to paint walls (toggle). Use the mode buttons to drop the start or goal. BFS finds the shortest path through free space — the gradient shows distance-from-start.
paint some walls, then run BFS
Directed acyclic graph — a robot boot sequence. Topological sort gives a valid order to run things so every dependency is satisfied. Toggle a cycle and watch the algorithm refuse to produce an order.
press run to compute boot order
The robot drove in a square but its odometry drifted. Each edge is a measurement constraint (relative dx, dy). The thick edge is the loop closure: "I'm back where I started." Iterate to relax the graph — every pose shifts to better satisfy the constraints.
press iterate to relax the graph
Multi-source BFS — drop "anchors" (charging stations, robot agents, sensors) and each non-anchor node is colored by its nearest anchor. That's a discrete Voronoi diagram. Used for territory assignment and coverage planning.
click nodes to add or remove anchors
Try it: Six modes, one demo. traverse: BFS vs DFS with the frontier visualized as a queue vs a stack — click any node to set start, then race both at once. shortest path: pick start + goal, compare Dijkstra to A* on a weighted graph. grid: paint walls, watch BFS plan around them. dag · topo: order a robot boot sequence; introduce a cycle and watch the algorithm refuse. pose graph: relax a drifted trajectory using a loop-closure constraint (toy SLAM). coverage: drop anchors, get a discrete Voronoi diagram.
Why it matters
Graphs model relationships: social networks, road maps, dependencies, state machines. BFS finds shortest paths, DFS explores all possibilities. Graphs are the universal language of connected data.
The idea
Think of a map of cities connected by roads. Each city is a vertex (node), each road is an edge (connection).
Graphs can be:
- Directed: One-way streets (Twitter follows)
- Undirected: Two-way roads (Facebook friends)
- Weighted: Roads with distances
Two representations:
- Adjacency matrix: 2D array where matrix[i][j] = 1 if edge exists. Fast lookup O(1), but O(V^2) space.
- Adjacency list: Each vertex stores a list of its neighbors. Space-efficient O(V+E).
Two traversals:
- BFS: Explore level by level, like ripples in a pond. Finds shortest path (unweighted).
- DFS: Go as deep as possible, then backtrack. Detects cycles, topological sort.
Key takeaways
- Vertices (nodes) + Edges (connections)
- Adjacency list is usually more space-efficient
- BFS finds shortest path, uses a queue
- DFS explores deeply, uses a stack (or recursion)
Going deeper
Topological sort — Kahn's algorithm and DFS-finish-times
A DAG (directed acyclic graph) represents dependencies. Topological sort linearizes the DAG so every edge u → v has u appearing before v in the output — i.e., every dependency comes before its dependents. Two algorithms, both O(V + E):
Kahn's algorithm (used in the demo's dag · topo mode):
1. Compute in-degree for every node.
2. Enqueue all nodes with in-degree 0 (no dependencies).
3. While the queue is non-empty:
u = dequeue. emit(u).
For each edge u → v:
in-degree[v] -= 1
if in-degree[v] == 0: enqueue(v).
4. If emitted fewer than V nodes → the graph contains a cycle.
DFS-finish-time variant: run DFS from every unvisited node; when a node finishes (all its descendants explored), push it on a stack. Reverse-pop the stack to get topological order. This one extends naturally to find strongly connected components (Kosaraju's, Tarjan's) — applications include finding clusters in social graphs and analyzing program control flow.
Robotics use: ROS launch files form a DAG (each node has prerequisite nodes that must start first); the launch system does topological sort under the hood. Behavior trees and Make/Bazel build graphs are the same shape.
Bellman-Ford — Dijkstra's negative-weight cousin
Dijkstra assumes non-negative edge weights. Its correctness proof relies on the fact that once you pop a node from the priority queue with the minimum cost-so-far, no shorter path could possibly arrive later. Negative edges break that assumption — a long but cheap detour could shorten a "settled" path.
Bellman-Ford trades speed for the ability to handle negative weights:
For each of V−1 iterations:
For each edge (u, v, w):
if dist[u] + w < dist[v]: dist[v] = dist[u] + w
That's O(V·E) — way slower than Dijkstra's O((V+E) log V) with a heap. The payoff is correctness under negative weights, and the ability to detect negative cycles (a V-th iteration that still relaxes some edge means a cycle exists with total negative weight — your shortest path would loop forever).
When you need it: currency arbitrage detection, route planning with toll refunds / subsidies, anything where edges can be "discounts." Most robotics navigation uses only non-negative costs (distance, energy, time), so Dijkstra and A* dominate.
Minimum spanning tree (MST)
For a connected, undirected, weighted graph, an MST is the subset of V − 1 edges that connects all vertices with minimum total weight. Two classic algorithms, both O(E log V):
- Kruskal's: sort all edges by weight ascending; greedily add each edge if it doesn't create a cycle. The cycle-check uses a union-find / disjoint-set structure (
O(α(n))per query, effectivelyO(1)). - Prim's: like Dijkstra. Start at any vertex; repeatedly extract the minimum-weight edge crossing from "in tree" to "out of tree." Priority queue keyed by edge weight.
When you need it: road / cable / pipeline layout, clustering (single-link clustering = building an MST then cutting the largest edges), network design. For robotics multi-agent coordination, MST gives a minimum-communication tree spanning all robots.
Representation choices
Adjacency-matrix vs adjacency-list isn't just space. They have different sweet spots:
- Matrix:
O(V²)space butO(1)edge-existence check. Use for dense graphs (E ≈ V²) or when you constantly ask "is u-v an edge?" — physics adjacency in dense grids, all-pairs shortest paths (Floyd-Warshall). - List:
O(V + E)space butO(degree)edge check. Use for sparse graphs (road networks, social networks, most real-world graphs).
Most production graph libraries default to lists. The Graph struct in the shortest-path demo above uses adjacency lists keyed by node id.
Math details
- Space Complexity
Adjacency Matrix: O(V^2)Adjacency List: O(V + E)- Time Complexity
BFS/DFS: O(V + E)Check edge (matrix): O(1)Check edge (list): O(degree)
Implementation
Graph Representations
// Adjacency List
let mut graph: Vec<Vec<usize>> = vec![vec![]; n];
graph[0].push(1); // Edge 0 -> 1
graph[1].push(0); // Edge 1 -> 0 (undirected)
// BFS from vertex 0
let mut visited = vec![false; n];
let mut queue = VecDeque::from([0]);
while let Some(v) = queue.pop_front() {
if visited[v] { continue; }
visited[v] = true;
for &neighbor in &graph[v] {
queue.push_back(neighbor);
}
}
Classic Algorithms
- BFS: Shortest path (unweighted)
- DFS: Cycle detection, topological sort
- Dijkstra: Shortest path (weighted)
- Kruskal/Prim: Minimum spanning tree
Practice
Three problems covering the three most-asked graph idioms:
- Number of Islands — medium · grid as graph, flood-fill, connected components
- Course Schedule — medium · DAG check via Kahn's topological sort; the algorithm behind every build system
- Shortest Path in a Maze — medium · BFS as wavefront expansion; the foundation of every robot path planner