Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Commit

Permalink
Corrected behavior with digraphs, created routine simplify
Browse files Browse the repository at this point in the history
  • Loading branch information
Michele Borassi committed Jul 16, 2015
1 parent dd7e575 commit 29346b2
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 98 deletions.
16 changes: 15 additions & 1 deletion src/sage/graphs/generic_graph.py
Expand Up @@ -3308,6 +3308,10 @@ def min_spanning_tree(self,
r"""
Returns the edges of a minimum spanning tree.

At the moment, no algorithm for directed graph is implemented: if the
graph is directed, a minimum spanning tree of the corresponding
undirected graph is returned.

INPUT:

- ``weight_function`` -- A function that takes an edge and returns a
Expand Down Expand Up @@ -3409,10 +3413,20 @@ def min_spanning_tree(self,
sage: g.min_spanning_tree(algorithm='NetworkX', weight_function=weight)
[(0, 2, 10), (1, 2, 1)]

If the graph is directed, it is transformed into an undirected graph::

sage: g = digraphs.Circuit(3)
sage: g.min_spanning_tree(weight_function=weight)
[(0, 2, None), (1, 2, None)]
sage: g.to_undirected().min_spanning_tree(weight_function=weight)
[(0, 2, None), (1, 2, None)]
"""
if algorithm == "Kruskal":
from spanning_tree import kruskal
return kruskal(self, wfunction=weight_function, check=check)
if self.is_directed():
return kruskal(self.to_undirected(), wfunction=weight_function, check=check)
else:
return kruskal(self, wfunction=weight_function, check=check)

if weight_function is None:
if self.weighted():
Expand Down
172 changes: 75 additions & 97 deletions src/sage/graphs/spanning_tree.pyx
Expand Up @@ -73,32 +73,64 @@ Methods

include "sage/ext/interrupt.pxi"

cpdef simplify(g):
r"""
Removes all self-loops and multiple edges from the input graph ``g``.
The graph ``g`` is not copied for performance reasons. If ``g`` is labeled
and edge ``(u,v)`` has many labels, only the minimum label will be kept.
EXAMPLE::
sage: from sage.graphs.spanning_tree import simplify
sage: g = Graph([[1,1,1],[1,2,2],[1,2,4],[2,3,3],[2,3,1]], multiedges=True, loops=True)
sage: simplify(g)
sage: g.edges()
[(1, 2, 2), (2, 3, 1)]
"""
g.allow_loops(False)
# If there are multiple edges from u to v, retain the edge of
# minimum weight among all such edges.
if g.allows_multiple_edges():
# If there are multiple edges from u to v, retain only the
# start and end vertices of such edges. Let a and b be the
# start and end vertices, respectively, of a weighted edge
# (a, b, w) having weight w. Then there are multiple weighted
# edges from a to b if and only if the set uniqE has the
# tuple (a, b) as an element.
uniqE = set()
for u, v, _ in iter(g.multiple_edges(to_undirected=True)):
uniqE.add((u, v))
# Let (u, v) be an element in uniqE. Then there are multiple
# weighted edges from u to v. Let W be a list of all edge
# weights of multiple edges from u to v, sorted in
# nondecreasing order. If w is the first element in W, then
# (u, v, w) is an edge of minimum weight (there may be
# several edges of minimum weight) among all weighted edges
# from u to v. If i >= 2 is the i-th element in W, delete the
# multiple weighted edge (u, v, i).
for u, v in uniqE:
W = sorted(g.edge_label(u, v))
for w in W[1:]:
g.delete_edge(u, v, w)
# all multiple edges should now be removed; check this!
assert g.multiple_edges() == []
g.allow_multiple_edges(False)


cpdef kruskal(G, wfunction=None, bint check=False):
r"""
Minimum spanning tree using Kruskal's algorithm.
This function assumes that we can only compute minimum spanning trees for
undirected simple graphs. Such graphs can be weighted or unweighted.
undirected graphs. Such graphs can be weighted or unweighted, and they can
have multiple edges (since we are computing the minimum spanning tree, only
the minimum weight among all `(u,v)`-edges is considered, for each pair
of vertices `u`, `v`).
INPUT:
- ``G`` -- A graph. This can be an undirected graph, a digraph, a
multigraph, or a multidigraph. Note the following behaviours:
- If ``G`` is unweighted, then consider the simple version of ``G``
with all self-loops and multiple edges removed.
- If ``G`` is directed, then we only consider its undirected version.
- If ``G`` is weighted and ``wfunction`` is not ``None``, the edge weights
are overridden by the function ``wfunction``. Otherwise, the input graph
should only have numeric weights. You cannot assign numeric weights to
some edges of ``G``, but have ``None`` as a weight for some other edge.
If your input graph is weighted, you are responsible for assign numeric
weight to each of its edges. Furthermore, we remove multiple edges as
follows. First we convert ``G`` to be undirected. Suppose there are
multiple edges from `u` to `v`. Among all such multiple edges, we choose
one with minimum weight.
- ``G`` -- an undirected graph.
- ``wfunction`` -- A weight function: a function that takes an edge and
returns a numeric weight. If ``wfunction=None`` (default), the algorithm
Expand All @@ -113,18 +145,18 @@ cpdef kruskal(G, wfunction=None, bint check=False):
- Is ``G`` the null graph?
- Is ``G`` disconnected?
- Is ``G`` a tree?
- Is ``G`` directed?
- Does ``G`` have self-loops?
- Does ``G`` have multiple edges?
By default, we turn off the sanity checks for performance reasons. This
means that by default the function assumes that its input graph is
undirected, simple, connected, is not a tree, and has at least one vertex.
If the input graph does not satisfy all of the latter conditions, you
should set ``check=True`` to perform some sanity checks and
preprocessing on the input graph. To further improve the runtime of this
function, you should call it directly instead of using it indirectly
via :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`.
connected, and has at least one vertex. Otherwise, you should set
``check=True`` to perform some sanity checks and preprocessing on the
input graph. If ``G`` has multiple edges or self-loops, the algorithm
still works, but the running-time can be improved if these edges are
removed. To further improve the runtime of this function, you should call
it directly instead of using it indirectly via
:meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`.
OUTPUT:
Expand All @@ -150,9 +182,6 @@ cpdef kruskal(G, wfunction=None, bint check=False):
sage: H = Graph(G.edges(labels=False))
sage: kruskal(H, check=True)
[(1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None), (3, 4, None), (4, 5, None)]
sage: H = DiGraph(G.edges(labels=False))
sage: kruskal(H, check=True)
[(1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None), (3, 4, None), (4, 5, None)]
sage: G.allow_loops(True)
sage: G.allow_multiple_edges(True)
sage: G
Expand Down Expand Up @@ -181,29 +210,6 @@ cpdef kruskal(G, wfunction=None, bint check=False):
sage: kruskal(G, check=True) == kruskal(H, check=True)
True
Note that we only consider an undirected version of the input graph. Thus
if ``G`` is a weighted multidigraph and ``H`` is an undirected version of
``G``, then this function should return the same minimum spanning tree
for both ``G`` and ``H``. ::
sage: from sage.graphs.spanning_tree import kruskal
sage: G = DiGraph({1:{2:[1,14,28], 6:[10]}, 2:{3:[16], 1:[15], 7:[14], 5:[20,21]}, 3:{4:[12,11]}, 4:{3:[13,3], 5:[22], 7:[18]}, 5:{6:[25], 7:[24], 2:[1,3]}}, multiedges=True)
sage: G.multiple_edges(to_undirected=False)
[(1, 2, 1), (1, 2, 14), (1, 2, 28), (5, 2, 1), (5, 2, 3), (4, 3, 3), (4, 3, 13), (3, 4, 11), (3, 4, 12), (2, 5, 20), (2, 5, 21)]
sage: H = G.to_undirected()
sage: H.multiple_edges(to_undirected=True)
[(1, 2, 1), (1, 2, 14), (1, 2, 15), (1, 2, 28), (2, 5, 1), (2, 5, 3), (2, 5, 20), (2, 5, 21), (3, 4, 3), (3, 4, 11), (3, 4, 12), (3, 4, 13)]
sage: kruskal(G, check=True)
[(1, 2, 1), (1, 6, 10), (2, 3, 16), (2, 5, 1), (2, 7, 14), (3, 4, 3)]
sage: kruskal(G, check=True) == kruskal(H, check=True)
True
sage: G.weighted(True)
sage: H.weighted(True)
sage: kruskal(G, check=True)
[(1, 2, 1), (1, 6, 10), (2, 3, 16), (2, 5, 1), (2, 7, 14), (3, 4, 3)]
sage: kruskal(G, check=True) == kruskal(H, check=True)
True
An example from pages 599--601 in [GoodrichTamassia2001]_. ::
sage: G = Graph({"SFO":{"BOS":2704, "ORD":1846, "DFW":1464, "LAX":337},
Expand Down Expand Up @@ -253,14 +259,6 @@ cpdef kruskal(G, wfunction=None, bint check=False):
[]
sage: kruskal(Graph(multiedges=True, loops=True), check=True)
[]
sage: kruskal(DiGraph(), check=True)
[]
sage: kruskal(DiGraph(multiedges=True), check=True)
[]
sage: kruskal(DiGraph(loops=True), check=True)
[]
sage: kruskal(DiGraph(multiedges=True, loops=True), check=True)
[]
The input graph must be connected. ::
Expand Down Expand Up @@ -308,7 +306,20 @@ cpdef kruskal(G, wfunction=None, bint check=False):
sage: T.edges() == kruskal(T, check=True) # long time
True
If the input is not a Graph::
sage: kruskal("I am not a graph")
Traceback (most recent call last):
...
ValueError: The input G must be an undirected graph.
sage: kruskal(digraphs.Path(10))
Traceback (most recent call last):
...
ValueError: The input G must be an undirected graph.
"""
from sage.graphs.graph import Graph
if not isinstance(G, Graph):
raise ValueError("The input G must be an undirected graph.")
g = G
sortedE_iter = None
# sanity checks
Expand All @@ -321,44 +332,11 @@ cpdef kruskal(G, wfunction=None, bint check=False):
if G.num_verts() == G.num_edges() + 1:
# G is a tree
return G.edges()
g = G.to_undirected()
g.allow_loops(False)
if g.weighted() and wfunction is None:
# If there are multiple edges from u to v, retain the edge of
# minimum weight among all such edges.
if g.allows_multiple_edges():
# By this stage, g is assumed to be an undirected, weighted
# multigraph. Thus when we talk about a weighted multiedge
# (u, v, w) of g, we mean that (u, v, w) and (v, u, w) are
# one and the same undirected multiedge having the same weight
# w.
# If there are multiple edges from u to v, retain only the
# start and end vertices of such edges. Let a and b be the
# start and end vertices, respectively, of a weighted edge
# (a, b, w) having weight w. Then there are multiple weighted
# edges from a to b if and only if the set uniqE has the
# tuple (a, b) as an element.
uniqE = set()
for u, v, _ in iter(g.multiple_edges(to_undirected=True)):
uniqE.add((u, v))
# Let (u, v) be an element in uniqE. Then there are multiple
# weighted edges from u to v. Let W be a list of all edge
# weights of multiple edges from u to v, sorted in
# nondecreasing order. If w is the first element in W, then
# (u, v, w) is an edge of minimum weight (there may be
# several edges of minimum weight) among all weighted edges
# from u to v. If i >= 2 is the i-th element in W, delete the
# multiple weighted edge (u, v, i).
for u, v in uniqE:
W = sorted(g.edge_label(u, v))
for w in W[1:]:
g.delete_edge(u, v, w)
# all multiple edges should now be removed; check this!
assert g.multiple_edges() == []
g.allow_multiple_edges(False)

# G is assumed to be connected, simple, undirected, and with at least a
# vertex
g = G.copy()
simplify(g)


# G is assumed to be connected, undirected, and with at least a vertex
# We sort edges, as specified.
if wfunction is None:
if g.weighted():
Expand Down

0 comments on commit 29346b2

Please sign in to comment.