Purpose: Solve the Steiner tree problem exactly: given a graph and a set of terminals, find the minimum-weight connected subgraph containing all terminals. Runs in β polynomial in , exponential only in .
Steiner tree
Unlike an MST, the tree may use extra βSteinerβ vertices not among the terminals. With it is a shortest path; with it is an MST; in between it is NP-hard.
Algorithm
State: dp[mask][v] = minimum cost of a tree that connects the terminal set mask and contains vertex .
Base: dp[{i}][t_i] = 0 for each terminal ; dp[{i}][v] = dist(t_i, v).
Two transitions:
- Merge two sub-trees meeting at the same vertex:
- Grow by moving the meeting point along an edge β a shortest-path relaxation over the whole graph for each mask:
Implement this with a Dijkstra seeded from every vertex with its currentdp[mask][Β·]value.
Answer: .
Code
const long long INF = 1e18;
long long steiner(int n, const vector<vector<pair<int,long long>>>& adj,
const vector<int>& term) {
int k = term.size(), FULL = 1 << k;
vector<vector<long long>> dp(FULL, vector<long long>(n, INF));
for (int i = 0; i < k; i++) dp[1 << i][term[i]] = 0;
for (int mask = 1; mask < FULL; mask++) {
// 1. merge sub-trees at the same vertex
for (int v = 0; v < n; v++)
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]);
// 2. Dijkstra to move the meeting point
priority_queue<pair<long long,int>, vector<pair<long long,int>>, greater<>> pq;
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:
- Space:
Practical up to - with in the thousands.
Correctness
Any optimal Steiner tree, rooted at any vertex it contains, either (a) has as a branching point, in which case it decomposes into two sub-trees each spanning a subset of the terminals β the merge transition; or (b) has as a degree-1 or pass-through vertex, in which case removing the edge at gives a smaller tree containing a neighbour β the grow transition. The DP considers both cases exhaustively. β
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
- 2-approximation β build the metric closure over terminals, take its MST, expand back to paths; ratio 2, polynomial. The best known ratio is .
- Steiner Tree β the topic page
- Held-Karp β the same βDP over subsets of a small special setβ pattern
- Network design, VLSI routing, phylogenetics β where Steiner trees originate