Rust and Data Structures

Graph Representations

A graph $G = (V, E)$ models arbitrary many-to-many relationships (edges $E$) between vertices $V$, including cycles and bidirectional links. Because arbitrary cycles violate single-owner tree semantics (Box<Node>), idiomatic Rust represents graphs using integer node indices (usize / u32) backed by contiguous vectors—avoiding Rc<RefCell<Node>> overhead while achieving superior CPU cache locality.

Directed Weighted Graph:   (0) ---[w=4]---> (1) ---[w=2]---> (2)
                            |                                 ^
                            +-------------[w=10]--------------+

1. Adjacency List (`Vec<Vec<(usize, u32)>>`):
   node 0: [ (1, 4), (2, 10) ]
   node 1: [ (2, 2) ]
   node 2: [ ]

2. Compressed Sparse Row / CSR (`offsets: Vec<usize>` + `edges: Vec<(usize, u32)>`):
   offsets: [ 0, 2, 3, 3 ]  (Node `u`'s edges live in `edges[offsets[u]..offsets[u+1]]`!)
   edges:   [ (1, 4), (2, 10), (2, 2) ]  (Single flat allocation for all edges!)

Comparing Graph Representations in Rust

Representation Rust Data Structure Memory Space Iterating Neighbors of $u$ Checking Edge $(u, v)$ Best Used For
Adjacency List Vec<Vec<Edge>> $O(|V| + |E|)$ $O(\text{deg}(u))$ $O(\text{deg}(u))$ Dynamic sparse graphs (BFS, DFS, Dijkstra, topological sort).
Compressed Sparse Row (CSR) offsets: Vec<usize> + edges: Vec<Edge> $O(|V| + |E|)$ (2 flat allocations total!) $O(\text{deg}(u))$ (Zero pointer chasing!) $O(\log(\text{deg}(u)))$ if sorted High-performance static/batch graph analytics (PageRank, routing).
Adjacency Matrix Vec<Weight> (|V| * |V|) $O(|V|^2)$ $O(|V|)$ $O(1)$ (mat[u * V + v]) Dense graphs where $|E| \approx |V|^2$ and $|V| \le 4,000$ (Floyd-Warshall).
Edge List Vec<(u32, u32, Weight)> $O(|E|)$ $O(|E|)$ $O(|E|)$ Kruskal's Minimum Spanning Tree & Bellman-Ford.

Implementing an Adjacency-List Graph with Dijkstra's Algorithm

use std::cmp::Reverse;
use std::collections::BinaryHeap;

#[derive(Debug, Clone, Copy)]
pub struct Edge {
    pub to: usize,
    pub weight: u64,
}

pub struct DiGraph {
    adj: Vec<Vec<Edge>>,
}

impl DiGraph {
    pub fn with_vertices(n: usize) -> Self {
        Self {
            adj: vec![Vec::new(); n],
        }
    }

    pub fn add_edge(&mut self, from: usize, to: usize, weight: u64) {
        self.adj[from].push(Edge { to, weight });
    }

    pub fn neighbors(&self, u: usize) -> &[Edge] {
        &self.adj[u]
    }

    /// Computes shortest path distances from `start` to all vertices using Dijkstra's algorithm.
    pub fn dijkstra(&self, start: usize) -> Vec<Option<u64>> {
        let mut dist: Vec<Option<u64>> = vec![None; self.adj.len()];
        let mut min_heap: BinaryHeap<Reverse<(u64, usize)>> = BinaryHeap::new();

        dist[start] = Some(0);
        min_heap.push(Reverse((0, start)));

        while let Some(Reverse((d, u))) = min_heap.pop() {
            if let Some(best) = dist[u] {
                if d > best {
                    continue; // Stale heap entry
                }
            }

            for &edge in self.neighbors(u) {
                let next_dist = d + edge.weight;
                if dist[edge.to].is_none_or(|cur| next_dist < cur) {
                    dist[edge.to] = Some(next_dist);
                    min_heap.push(Reverse((next_dist, edge.to)));
                }
            }
        }

        dist
    }
}

fn main() {
    let mut g = DiGraph::with_vertices(4);
    g.add_edge(0, 1, 4);
    g.add_edge(0, 2, 10);
    g.add_edge(1, 2, 2);
    g.add_edge(2, 3, 3);

    let distances = g.dijkstra(0);
    // Path 0 -> 1 -> 2 -> 3 has cost 4 + 2 + 3 = 9 (beating direct 0 -> 2 -> 3 = 13)
    assert_eq!(distances, vec![Some(0), Some(4), Some(6), Some(9)]);
    println!("Shortest distances from vertex 0: {distances:?}");
}
Shortest distances from vertex 0: [Some(0), Some(4), Some(6), Some(9)]