N or P
Contents Glossary
08

Routing — the shortest path through a network

Routing problems are a settled category — and one of the most common places engineering teams waste weeks reinventing the standard answer.

Routing problems — given a network with costs on each link, find the cheapest path from one point to another — are completely solved when all costs are non-negative. The algorithm is named for Edsger Dijkstra, who published it in 1959. Every map application, every game pathfinder, every logistics platform uses it. Cost grows roughly with the size of the network — a network ten times bigger takes about eleven times longer to route through. Rust's petgraph and pathfinding libraries both implement it as a one-line call. When your team estimates more than a day on a routing feature with non-negative costs, they are either solving a different problem (negative costs? See page 09. Multiple stops in any order? Page 25) or they are not using the library.

  1. 01
    Build the road graph as a DiGraph with travel-minute edge weights, then call dijkstra once. The return is a HashMap from each reachable intersection to the cheapest distance. Sixty years of algorithms, four lines of glue.
    use petgraph::algo::dijkstra;
    use petgraph::graph::{DiGraph, NodeIndex};
    
    fn fastest_routes(road: &DiGraph<&str, u32>, from: NodeIndex)
        -> std::collections::HashMap<NodeIndex, u32>
    {
        dijkstra(road, from, None, |e| *e.weight())
    }
  2. 02
    |e| *e.weight() is a closure — Rust's name for an anonymous function. Other languages call this a lambda. The pipes | | wrap the parameter list; everything after is the body. Here the closure takes one parameter e (a reference to an edge), calls e.weight() to get &u32 (a reference to the weight stored inside the graph), then uses the prefix * — the dereference operator — to read the underlying u32 out of that reference. The closure tells Dijkstra how to extract a cost from each edge; swap it for a different closure and the algorithm uses a different cost.
    // |e| *e.weight()
    //  │    │
    //  │    └── body: dereference the &u32 to get a u32
    //  └──────── parameter list: one parameter named e (an edge reference)
    
    // Same thing written as a named function:
    fn extract_weight(e: petgraph::graph::EdgeReference<u32>) -> u32 {
        *e.weight()
    }
    
    // You can swap in any cost function you want:
    dijkstra(road, from, None, |e| *e.weight());        // real travel minutes
    dijkstra(road, from, None, |e| *e.weight() * 2);    // every road takes twice as long
    dijkstra(road, from, None, |_| 1u32);               // ignore weights — count hops
  3. 03
    Input shape: a directed graph where each node is an intersection and each edge has a u32 weight (travel minutes). Output shape: a HashMap<NodeIndex, u32> — for every node reachable from from, the cheapest total cost to get there. Unreachable nodes are simply absent from the map. Below is a four-intersection example with Dijkstra run from A — notice that D is reached cheaper via A → B → D (cost 3) than via A → C → D (cost 13), and the map records only the winning total.
    Input — &DiGraph<&str, u32>:
    
          A ──2──▶ B ──1──▶ D
          │                 ▲
          5                 8
          │                 │
          ▼                 │
          C ────────────────┘
    
    Edges in the graph:
        A → B   2
        A → C   5
        B → D   1
        C → D   8
    
    
    Call:  dijkstra(road, A, None, |e| *e.weight())
    
    
    Output — HashMap<NodeIndex, u32>:
    
        {
            A → 0,   // start: zero cost to reach yourself
            B → 2,   // A → B
            C → 5,   // A → C
            D → 3,   // A → B → D = 2 + 1   (beats A → C → D = 5 + 8 = 13)
        }
Dijkstra, E. (1959) A Note on Two Problems in Connexion with Graphs. Source →
In plain terms

Edsger Dijkstra invented the shortest-path algorithm in twenty minutes at a café in Amsterdam in 1956. He published it three years later. Sixty-five years later, it is the engine inside every map application, every car navigation system, every game pathfinder, every logistics platform, every internet routing protocol. It is one of the most successful algorithms in the history of computing.

For a business planning purposes, what matters is the shape of the problem it solves. You have a network of points connected by paths, each path with a cost — distance, time, dollars, latency. You want the cheapest way to get from one point to another. As long as no path has negative cost (no reverse-arbitrage), Dijkstra's algorithm finds the answer in time roughly proportional to the size of the network.

The key word is roughly. Doubling the network size does not double the work, it slightly more than doubles it — the work grows with the network size times the logarithm of the number of points. In practice, this means a routing query over a road network of a million intersections takes milliseconds on a single laptop core. Routing over a hundred million takes a couple of seconds.

In Rust, the algorithm is a one-line call against either the petgraph library or the pathfinding library. Your team writes a function that, given a point, returns its neighbors and the cost of each edge. The library handles the rest.

Where routing becomes harder is when the problem is not actually shortest-path. If you need to visit many points in any order, that is the Traveling Salesman problem and it is genuinely hard (page 25). If you have multiple vehicles with capacity constraints, that is vehicle routing and you should buy a SaaS like Vroom or OptiMoR. If the costs change in real time, that is dynamic routing and you need a different approach.

But for plain "cheapest path from A to B" with positive costs, the answer is settled. Use the library.

← 07 Traversing a network
8/39
09 Routing when paths can have negative costs →