# HG changeset patch
# User Nathann Cohen
# Date 1267379885 -3600
# Node ID 1a774059334288a72aaf5e897fb146eb56e41016
# Parent 556bb66e4c6dbb92a4ee37c1750d82a5c6298eeb
trac 8403 : Graph.steiner_tree
diff -r 556bb66e4c6d -r 1a7740593342 sage/graphs/generic_graph.py
--- a/sage/graphs/generic_graph.py Mon Jun 07 00:22:02 2010 -0700
+++ b/sage/graphs/generic_graph.py Sun Feb 28 18:58:05 2010 +0100
@@ -3086,6 +3086,156 @@
v = pv
return B, C
+
+
+ def steiner_tree(self,vertices, weighted = False):
+ r"""
+ Returns a tree of minimum weight connecting the given
+ set of vertices.
+
+ Definition :
+
+ Computing a minimum spanning tree in a graph can be done in `n
+ \log(n)` time (and in linear time if all weights are
+ equal). On the other hand, if one is given a large (possibly
+ weighted) graph and a subset of its vertices, it is NP-Hard to
+ find a tree of minimum weight connecting the given set of
+ vertices, which is then called a Steiner Tree.
+
+ `Wikipedia article on Steiner Trees
+ `_.
+
+ INPUT:
+
+ - ``vertices`` -- the vertices to be connected by the Steiner
+ Tree.
+
+ - ``weighted`` (boolean) -- Whether to consider the graph as
+ weighted, and use each edge's label as a weight, considering
+ ``None`` as a weight of `1`. If ``weighted=False`` (default)
+ all edges are considered to have a weight of `1`.
+
+ .. NOTE::
+
+ * This problem being defined on undirected graphs, the
+ orientation is not considered if the current graph is
+ actually a digraph.
+
+ * The graph is assumed not to have multiple edges.
+
+ ALGORITHM:
+
+ Solved through Linear Programming.
+
+ COMPLEXITY:
+
+ NP-Hard.
+
+ Note that this algorithm first checks whether the given
+ set of vertices induces a connected graph, returning one of its
+ spanning trees if ``weighted`` is set to ``False``, and thus
+ answering very quickly in some cases
+
+ EXAMPLES:
+
+ The Steiner Tree of the first 5 vertices in a random graph is,
+ of course, always a tree ::
+
+ sage: g = graphs.RandomGNP(30,.5)
+ sage: st = g.steiner_tree(g.vertices()[:5]) # optional - requires GLPK, CBC or CPLEX
+ sage: st.is_tree() # optional - requires GLPK, CBC or CPLEX
+ True
+
+ And all the 5 vertices are contained in this tree ::
+
+ sage: all([v in st for v in g.vertices()[:5] ]) # optional - requires GLPK, CBC or CPLEX
+ True
+
+ An exception is raised when the problem is impossible, i.e.
+ if the given vertices are not all included in the same
+ connected component ::
+
+ sage: g = 2 * graphs.PetersenGraph()
+ sage: st = g.steiner_tree([5,15])
+ Traceback (most recent call last):
+ ...
+ ValueError: The given vertices do not all belong to the same connected component. This problem has no solution !
+ """
+
+ if self.is_directed():
+ g = Graph(self)
+ else:
+ g = self
+
+ if g.has_multiple_edges():
+ raise ValueError("The graph is expected not to have multiple edges.")
+
+ # Can the problem be solved ? Are all the vertices in the same
+ # connected component ?
+ cc = g.connected_component_containing_vertex(vertices[0])
+ if not all([v in cc for v in vertices]):
+ raise ValueError("The given vertices do not all belong to the same connected component. This problem has no solution !")
+
+ # Can it be solved using the min spanning tree algorithm ?
+ if not weighted:
+ gg = g.subgraph(vertices)
+ if gg.is_connected():
+ st = g.subgraph(edges = gg.min_spanning_tree())
+ st.delete_vertices([v for v in g if st.degree(v) == 0])
+ return st
+
+ # Then, LP formulation
+ from sage.numerical.mip import MixedIntegerLinearProgram
+ p = MixedIntegerLinearProgram(maximization = False)
+
+ # Reorder an edge
+ R = lambda (x,y) : (x,y) if x