N or P
Contents Glossary
11

Cheapest network that connects everything

Connect every point with the cheapest wires that form no loop. This is one of the few business problems where greedy is provably optimal.

When you need to connect a set of points — fiber backbone, electrical grid, road network, communication mesh — with the minimum total length of cable or path, the problem is a minimum spanning tree. Two algorithms find the exact best answer: sort all possible connections by cost and add them in order, skipping any that would form a redundant loop (Kruskal 1956); or grow a tree from any starting point, always adding the cheapest connection to a new point (Prim 1957). Both are fast — work grows roughly with the number of possible connections. Rust's petgraph has both. When the problem changes to "connect this subset, using others as relays if it saves money," it becomes the Steiner tree problem and is genuinely hard (NP-complete, see page 38 for the contrast). The line between cheap and expensive is whether every point must be connected or only a subset.

  1. 01
    min_spanning_tree returns the edges of the optimal tree as an iterator you can fold straight into a new graph. For a fiber-rollout plan, the input is locations with cable costs and the output is the cheapest provably-complete network.
    use petgraph::algo::min_spanning_tree;
    use petgraph::data::FromElements;
    use petgraph::graph::UnGraph;
    
    fn cheapest_fiber(sites: &UnGraph<&str, u32>) -> UnGraph<&str, u32> {
        UnGraph::from_elements(min_spanning_tree(sites))
    }
  2. 02
    A turbofish is the syntax ::<T> — Rust's way of specifying generic type parameters at a callsite. Most of the time the compiler infers generic parameters from context, so you do not write the turbofish; it is the escape hatch for when inference is not enough. The name is folklore: ::<> looks like a tiny fish swimming to the right — two dots for eyes, angle brackets for fins. The call UnGraph::from_elements(min_spanning_tree(sites)) above works without a turbofish because the function signature -> UnGraph<&str, u32> pins down the type parameters for inference. Strip the signature away and the turbofish has to surface.
    // Anatomy:
    //
    //        parse :: < i32 > ()
    //        ───── ━━ ━━━━━━━ ──
    //        name      type    call
    //              ┗━━━━━┳━━━━┛
    //                    └─ turbofish:  ::<T>
    //
    // Two dots (eyes), two angle brackets (fins).  ><> ::<T>
    
    // Two places it commonly appears:
    
    // 1.  parse() — the return type is the only thing that decides which
    //     parser to call, and the compiler can't always infer it.
    let n = "42".parse::<i32>().unwrap();      // turbofish on parse
    let n: i32 = "42".parse().unwrap();        // or annotate the binding
    
    // 2.  collect() — the iterator gives no hint about which container
    //     you want to materialise into.
    let v = (1..=5).collect::<Vec<i32>>();     // turbofish on collect
    let v: Vec<i32> = (1..=5).collect();       // or annotate the binding
    
    // Page 11's call works without it because the function's return type
    // pins down the generics:
    fn cheapest_fiber(sites: &UnGraph<&str, u32>) -> UnGraph<&str, u32> {
        UnGraph::from_elements(min_spanning_tree(sites))   // inferred
    }
    
    // Same thing written with the turbofish — equivalent, more verbose:
    fn cheapest_fiber_explicit(sites: &UnGraph<&str, u32>) -> UnGraph<&str, u32> {
        UnGraph::<&str, u32>::from_elements(min_spanning_tree(sites))
    //           ▲▲▲▲▲▲▲▲▲▲▲▲
    //           the turbofish
    }
Kruskal, J. (1956). Prim, R. (1957). Borůvka, O. (1926) is the oldest. Source →
In plain terms

Connecting a set of locations with the cheapest possible total infrastructure is a classic business problem. Telecom companies solving fiber rollout. Utilities planning the grid. Cloud providers planning data center interconnect. Logistics companies planning trunk routes. All minimum spanning tree problems, all solved.

The algorithms are about as simple as algorithms get. Kruskal's version: list every possible connection with its cost, sort the list cheapest first, add connections in order, skip any that would create a loop. Stop when every location is connected. The result is provably the minimum total cost. Prim's version: start at any one location, repeatedly add the cheapest connection that reaches a new location. Same answer.

The reason greedy works here — and almost nowhere else for genuinely combinatorial problems — is a structural property of spanning trees called the cut property. For any split of the locations into two groups, the cheapest connection crossing the split must be in some optimal spanning tree. Both algorithms exploit this fact. The result is one of the cleanest examples of a greedy algorithm being provably optimal.

In Rust, petgraph's min_spanning_tree function does the work. Your team feeds in a graph; the library returns the edges of the optimal tree. This is a half-day feature, not a quarter-long project.

The trap to watch for is a variant that looks similar but is actually much harder. Steiner tree is the problem where you only need to connect a specified subset of locations — and you are allowed to use other locations as relays if it saves money. This added flexibility breaks the greedy structure. Steiner tree is NP-complete. The problem looks innocent in a planning meeting ("connect just these five offices, but route through others if cheaper") and it is the wrong problem to underestimate.

If every location must be on the network, the work is cheap. If only a subset must be, the work is expensive. Read the requirement carefully.

See page 38 for the cluster of "cheap twin vs expensive twin" pairs. This is one of the most important.

← 10 Distance from everywhere to everywhere
11/39
12 Flow, capacity, and bottlenecks →