Given a graph and a set of terminals, find the minimum-weight connected subgraph containing all terminals. It may use extra (“Steiner”) vertices.

Dreyfus-Wagner — exact,

DP over subsets of terminals: dp[mask][v] = the minimum cost of a tree connecting the terminal set mask and containing vertex .

Base: for each terminal .

Two transitions:

  1. Merge two sub-trees meeting at : .
  2. Grow by moving the meeting point along an edge — a Dijkstra over the whole graph for each mask, seeded from every vertex with its current dp[mask][·] value: .

Answer: .

const ll INF = 1e18;
 
ll steiner(int n, const vector<vector<pair<int, ll>>>& adj, const vector<int>& term) {
    int k = term.size(), FULL = 1 << k;
    vector<vector<ll>> dp(FULL, vector<ll>(n, INF));
    for (int i = 0; i < k; i++) dp[1 << i][term[i]] = 0;
 
    for (int mask = 1; mask < FULL; mask++) {
        for (int v = 0; v < n; v++)                                   // 1. merge sub-trees at the same vertex
            for (int sub = (mask - 1) & mask; sub; sub = (sub - 1) & mask)
                if (dp[sub][v] < INF && dp[mask ^ sub][v] < INF)
                    dp[mask][v] = min(dp[mask][v], dp[sub][v] + dp[mask ^ sub][v]);
 
        priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> pq;   // 2. Dijkstra to move the meeting point
        for (int v = 0; v < n; v++) if (dp[mask][v] < INF) pq.push({dp[mask][v], v});
        while (!pq.empty()) {
            auto [d, u] = pq.top(); pq.pop();
            if (d > dp[mask][u]) continue;
            for (auto [v, w] : adj[u]) if (d + w < dp[mask][v]) { dp[mask][v] = d + w; pq.push({dp[mask][v], v}); }
        }
    }
    return *min_element(dp[FULL - 1].begin(), dp[FULL - 1].end());
}

Complexity: subset enumeration , times vertices ; Dijkstra per mask, masks, ; space . Practical up to - with in the thousands.

Tested: steiner checked against brute force (enumerate every vertex subset containing all terminals, take the MST of the induced subgraph if connected, keep the minimum over all subsets) on small random graphs with up to 10 vertices and up to 4 terminals.

Approximation: the metric-closure MST — a 2-approximation

  1. Compute all-pairs shortest paths among the terminals (one Dijkstra per terminal).
  2. Build the complete graph on the terminals with those distances.
  3. Take its MST.
  4. Expand each MST edge back into its shortest path.
// for each terminal t: dijkstra(t)                       // k runs, reusing an already-tested Dijkstra
// build the complete graph on terminals with dist[i][j]
// mst = kruskal(that graph)                               // reusing an already-tested Kruskal
// expand each MST edge into its shortest path

The best known ratio is (Byrka et al.), via LP rounding.

The variants

VariantComplexity
shortest path, P
MST, P
General NP-hard
smallFPT:
Steiner tree in a treeP — the answer is the virtual tree of the terminals
Steiner forest (pairs to connect)NP-hard; 2-approximation by primal-dual
Directed Steiner tree (arborescence)much harder; no constant-factor approximation known
Euclidean Steiner treeNP-hard; PTAS exists; Steiner points meet at
Rectilinear Steiner treeNP-hard; VLSI routing
Prize-collecting Steiner treeNP-hard; 2-approximation
Node-weighted Steiner tree-approximable

Steiner tree in a tree

When the graph is itself a tree, the minimum connected subgraph containing the terminals is simply the union of the paths between them — computable in with a virtual tree:

Equivalently, sort the terminals by DFS entry time and sum around the cycle, halved:

sort(terms.begin(), terms.end(), byTin);
ll total = 0;
for (int i = 0; i < k; i++) total += dist(terms[i], terms[(i + 1) % k]);
total /= 2;

Tested: the cyclic-sum formula checked against brute-force search over all subsets of tree edges (keep the minimum-weight edge subset connecting all terminals) on random trees with up to 15 vertices and up to 6 terminals.

Where it appears

  • Network design — connect a set of sites at minimum cable cost.
  • VLSI routing — the rectilinear version.
  • Phylogenetics — minimum evolution trees.
  • Contest problems — usually with terminals, signalling Dreyfus-Wagner.

The recognition cue

When a problem says “connect these special vertices at minimum cost” and is suspiciously small () while is large, it is a Steiner tree and the intended solution is the DP. That constraint pattern is the giveaway.

Variants / Use Cases

  • Steiner tree in a grid / on a plane — the geometric version (Euclidean Steiner tree) is also NP-hard, with a different structure.
  • Held-Karp — the same “DP over subsets of a small special set” pattern.
  • Network design, VLSI routing, phylogenetics — where Steiner trees originate.