From 29346b2a53f3c77a56d3ac864be47820ab32b460 Mon Sep 17 00:00:00 2001 From: Michele Borassi Date: Thu, 16 Jul 2015 10:01:27 +0200 Subject: [PATCH] Corrected behavior with digraphs, created routine simplify --- src/sage/graphs/generic_graph.py | 16 ++- src/sage/graphs/spanning_tree.pyx | 172 +++++++++++++----------------- 2 files changed, 90 insertions(+), 98 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 99eaa78a477..c275639a585 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -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 @@ -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(): diff --git a/src/sage/graphs/spanning_tree.pyx b/src/sage/graphs/spanning_tree.pyx index 6668dad0629..bbb6c623c6b 100644 --- a/src/sage/graphs/spanning_tree.pyx +++ b/src/sage/graphs/spanning_tree.pyx @@ -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 @@ -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: @@ -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 @@ -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}, @@ -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. :: @@ -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 @@ -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():