The Steiner Tree algorithm finds a minimum-weight tree that connects a set of terminal nodes through a specified root node. Unlike a minimum spanning tree which connects all nodes, a Steiner tree only needs to connect the specified terminals — it may include additional intermediate nodes (called Steiner nodes) if they help reduce the total weight.
Applications:
Given a graph, a root node, and a set of terminal nodes, the Steiner tree is the minimum-weight subgraph that connects the root to all terminals. The problem is NP-hard, so this implementation uses an approximation:
This heuristic may not find the optimal Steiner tree, but runs efficiently and produces good results in practice.
GQLINSERT (A:default {_id: "A"}), (B:default {_id: "B"}), (C:default {_id: "C"}), (D:default {_id: "D"}), (E:default {_id: "E"}), (F:default {_id: "F"}), (G:default {_id: "G"}), (H:default {_id: "H"}), (A)-[:default {distance: 1}]->(B), (A)-[:default {distance: 2.4}]->(C), (A)-[:default {distance: 1.2}]->(D), (A)-[:default {distance: 0.7}]->(E), (A)-[:default {distance: 2.2}]->(F), (A)-[:default {distance: 1.6}]->(G), (A)-[:default {distance: 0.4}]->(H), (B)-[:default {distance: 1.3}]->(C), (C)-[:default {distance: 1}]->(D), (D)-[:default {distance: 1.65}]->(H), (E)-[:default {distance: 1.27}]->(F), (E)-[:default {distance: 0.9}]->(G), (F)-[:default {distance: 0.45}]->(G)
| Name | Type | Default | Description |
|---|---|---|---|
rootNode | STRING | / | Required. Root node _id. |
terminalNodes | LIST | / | Required. List of terminal node _ids that must be connected. |
weightProperty | STRING | / | Edge property to use as weight. If unset, all edges have unit weight. |
Returns:
| Column | Type | Description |
|---|---|---|
sourceId | STRING | Source node of the Steiner tree edge |
targetId | STRING | Target node of the Steiner tree edge |
weight | FLOAT | Edge weight |
GQLCALL algo.steiner({ rootNode: "A", terminalNodes: ["C", "F"], weightProperty: "distance" }) YIELD sourceId, targetId, weight
Result:
| sourceId | targetId | weight |
|---|---|---|
| A | B | 1 |
| B | C | 1.3 |
| A | E | 0.7 |
| E | F | 1.27 |
Returns the same columns as run mode, streamed for memory efficiency.
GQLCALL algo.steiner.stream({ rootNode: "A", terminalNodes: ["C", "F", "E"], weightProperty: "distance" }) YIELD sourceId, targetId, weight RETURN sourceId, targetId, weight
Result:
| sourceId | targetId | weight |
|---|---|---|
| A | B | 1 |
| B | C | 1.3 |
| A | E | 0.7 |
| E | F | 1.27 |
Terminal E is already on the path A→E→F, so adding it doesn't introduce new edges.
Returns:
| Column | Type | Description |
|---|---|---|
edgeCount | INT | Number of edges in the Steiner tree |
totalWeight | FLOAT | Total weight of the Steiner tree |
GQLCALL algo.steiner.stats({ rootNode: "A", terminalNodes: ["C", "F", "E"], weightProperty: "distance" }) YIELD edgeCount, totalWeight
Result:
| edgeCount | totalWeight |
|---|---|
| 4 | 4.27 |