Ticket #10433: trac-10433-rebased-4.6.2.alpha0.patch

File trac-10433-rebased-4.6.2.alpha0.patch, 30.8 KB (added by ncohen, 9 years ago)
  • doc/en/reference/graphs.rst

    # HG changeset patch
    # User Minh Van Nguyen <nguyenminh2@gmail.com>
    # Date 1291779407 28800
    # Node ID 574805b94334332c992fc7414dcdc3745739c4f3
    # Parent  4a9f7b41ae22e247c2a6675c7854e419f7340d09
    #10433: clean up the code for Kruskal's algorithm
    
    * List the new module spanning_tree.pyx in the Sage reference manual.
    * Move the method Graph.min_spanning_tree() to GenericGraph as a method
      of the latter class.
    * If GenericGraph.min_spanning_tree() is to use Kruskal's algorithm, get
      that method to call spanning_tree.kruskal().
    * Some PEP 8 clean-up in GenericGraph.min_spanning_tree().
    * Describe the overall goal of the new module spanning_tree.pyx.
    * Some clean-up of documentation of min_spanning_tree().
    * Cross reference graphs.generic_graph.GenericGraph.min_spanning_tree()
      and graphs.spanning_tree.kruskal().
    * Raise the upper bound for doctest in misc.sagedoc.search_doc.
      Docstrings for graphs.spanning_tree add many more counts of the word
      "tree".
    
    diff -r 4a9f7b41ae22 -r 574805b94334 doc/en/reference/graphs.rst
    a b  
    4444
    4545   sage/graphs/graph_coloring
    4646   sage/graphs/cliquer
     47   sage/graphs/spanning_tree
    4748   sage/graphs/pq_trees
    4849   sage/graphs/matchpoly
    4950   sage/graphs/graph_latex
  • module_list.py

    diff -r 4a9f7b41ae22 -r 574805b94334 module_list.py
    a b  
    380380                         'sage/graphs/planarity/platformTime.h',
    381381                         'sage/graphs/planarity/stack.h']),
    382382
     383    Extension('sage.graphs.spanning_tree',
     384              sources = ['sage/graphs/spanning_tree.pyx']),
     385
    383386    Extension('sage.graphs.trees',
    384387              sources = ['sage/graphs/trees.pyx']),
    385388
  • sage/graphs/generic_graph.py

    diff -r 4a9f7b41ae22 -r 574805b94334 sage/graphs/generic_graph.py
    a b  
    20292029                else:
    20302030                    return d
    20312031
     2032    def min_spanning_tree(self,
     2033                          weight_function=lambda e: 1,
     2034                          algorithm="Kruskal",
     2035                          starting_vertex=None,
     2036                          check=False):
     2037        r"""
     2038        Returns the edges of a minimum spanning tree.
     2039
     2040        INPUT:
     2041
     2042        - ``weight_function`` -- A function that takes an edge and returns a
     2043          numeric weight. Defaults to assigning each edge a weight of 1.
     2044
     2045        - ``algorithm`` -- The algorithm to use in computing a minimum sapnning
     2046          tree of ``G``. The default is to use Kruskal's algorithm. The
     2047          following algorithms are supported:
     2048
     2049          - ``"Kruskal"`` -- Kruskal's algorithm.
     2050
     2051          - ``"Prim_fringe"`` -- a variant of Prim's algorithm.
     2052            ``"Prim_fringe"`` ignores the labels on the edges.
     2053
     2054          - ``"Prim_edge"`` -- a variant of Prim's algorithm.
     2055
     2056          - ``NetworkX`` -- Uses NetworkX's minimum spanning tree
     2057            implementation.
     2058
     2059        - ``starting_vertex`` -- The vertex from which to begin the search
     2060          for a minimum spanning tree.
     2061
     2062        - ``check`` -- Boolean; default: ``False``. Whether to first perform
     2063          sanity checks on the input graph ``G``. If appropriate, ``check``
     2064          is passed on to any minimum spanning tree functions that are
     2065          invoked from the current method. See the documentation of the
     2066          corresponding functions for details on what sort of sanity checks
     2067          will be performed.
     2068
     2069        OUTPUT:
     2070
     2071        The edges of a minimum spanning tree of ``G``, if one exists, otherwise
     2072        returns the empty list.
     2073
     2074        .. seealso::
     2075
     2076            - :func:`sage.graphs.spanning_tree.kruskal`
     2077
     2078        EXAMPLES:
     2079
     2080        Kruskal's algorithm::
     2081
     2082            sage: g = graphs.CompleteGraph(5)
     2083            sage: len(g.min_spanning_tree())
     2084            4
     2085            sage: weight = lambda e: 1 / ((e[0] + 1) * (e[1] + 1))
     2086            sage: g.min_spanning_tree(weight_function=weight)
     2087            [(3, 4, {}), (2, 4, {}), (1, 4, {}), (0, 4, {})]
     2088            sage: g = graphs.PetersenGraph()
     2089            sage: g.allow_multiple_edges(True)
     2090            sage: g.weighted(True)
     2091            sage: g.add_edges(g.edges())
     2092            sage: g.min_spanning_tree()
     2093            [(0, 1, None), (0, 4, None), (0, 5, None), (1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None), (3, 8, None), (4, 9, None)]
     2094
     2095        Prim's algorithm::
     2096
     2097            sage: g = graphs.CompleteGraph(5)
     2098            sage: g.min_spanning_tree(algorithm='Prim_edge', starting_vertex=2, weight_function=weight)
     2099            [(2, 4, {}), (3, 4, {}), (1, 4, {}), (0, 4, {})]
     2100            sage: g.min_spanning_tree(algorithm='Prim_fringe', starting_vertex=2, weight_function=weight)
     2101            [(2, 4), (4, 3), (4, 1), (4, 0)]
     2102        """
     2103        if algorithm == "Kruskal":
     2104            from spanning_tree import kruskal
     2105            return kruskal(self, wfunction=weight_function, check=check)
     2106        elif algorithm == "Prim_fringe":
     2107            if starting_vertex is None:
     2108                v = self.vertex_iterator().next()
     2109            else:
     2110                v = starting_vertex
     2111            tree = set([v])
     2112            edges = []
     2113            # Initialize fringe_list with v's neighbors. Fringe_list
     2114            # contains fringe_vertex: (vertex_in_tree, weight) for each
     2115            # fringe vertex.
     2116            fringe_list = dict([u, (weight_function((v, u)), v)] for u in self[v])
     2117            cmp_fun = lambda x: fringe_list[x]
     2118            for i in range(self.order() - 1):
     2119                # find the smallest-weight fringe vertex
     2120                u = min(fringe_list, key=cmp_fun)
     2121                edges.append((fringe_list[u][1], u))
     2122                tree.add(u)
     2123                fringe_list.pop(u)
     2124                # update fringe list
     2125                for neighbor in [v for v in self[u] if v not in tree]:
     2126                    w = weight_function((u, neighbor))
     2127                    if neighbor not in fringe_list or fringe_list[neighbor][0] > w:
     2128                        fringe_list[neighbor] = (w, u)
     2129            return edges
     2130        elif algorithm == "Prim_edge":
     2131            if starting_vertex is None:
     2132                v = self.vertex_iterator().next()
     2133            else:
     2134                v = starting_vertex
     2135            sorted_edges = sorted(self.edges(), key=weight_function)
     2136            tree = set([v])
     2137            edges = []
     2138            for _ in range(self.order() - 1):
     2139                # Find a minimum-weight edge connecting a vertex in the tree
     2140                # to something outside the tree. Remove the edges between
     2141                # tree vertices for efficiency.
     2142                i = 0
     2143                while True:
     2144                    e = sorted_edges[i]
     2145                    v0, v1 = e[0], e[1]
     2146                    if v0 in tree:
     2147                        del sorted_edges[i]
     2148                        if v1 in tree:
     2149                            continue
     2150                        edges.append(e)
     2151                        tree.add(v1)
     2152                        break
     2153                    elif v1 in tree:
     2154                        del sorted_edges[i]
     2155                        edges.append(e)
     2156                        tree.add(v0)
     2157                        break
     2158                    else:
     2159                        i += 1
     2160            return edges
     2161        elif algorithm == "NetworkX":
     2162            import networkx
     2163            G = networkx.Graph([(u, v, dict(weight=weight_function((u, v)))) for u, v, l in self.edge_iterator()])
     2164            return list(networkx.mst(G))
     2165        else:
     2166            raise NotImplementedError("Minimum Spanning Tree algorithm '%s' is not implemented." % algorithm)
     2167
    20322168    def spanning_trees_count(self, root_vertex=None):
    20332169        """
    20342170        Returns the number of spanning trees in a graph. In the case of a
  • sage/graphs/graph.py

    diff -r 4a9f7b41ae22 -r 574805b94334 sage/graphs/graph.py
    a b  
    34123412
    34133413    ### Miscellaneous
    34143414
    3415     def min_spanning_tree(self, weight_function=lambda e: 1,
    3416                           algorithm='Kruskal',
    3417                           starting_vertex=None ):
    3418         """
    3419         Returns the edges of a minimum spanning tree, if one exists,
    3420         otherwise returns False.
    3421        
    3422         INPUT:
    3423        
    3424        
    3425         -  ``weight_function`` - A function that takes an edge
    3426            and returns a numeric weight. Defaults to assigning each edge a
    3427            weight of 1.
    3428        
    3429         -  ``algorithm`` - Three variants of algorithms are
    3430            implemented: 'Kruskal', 'Prim fringe', and 'Prim edge' (the last
    3431            two are variants of Prim's algorithm). Defaults to 'Kruskal'.
    3432            Currently, 'Prim fringe' ignores the labels on the edges.
    3433        
    3434         -  ``starting_vertex`` - The vertex with which to
    3435            start Prim's algorithm.
    3436        
    3437        
    3438         OUTPUT: the edges of a minimum spanning tree.
    3439        
    3440         EXAMPLES::
    3441        
    3442             sage: g=graphs.CompleteGraph(5)
    3443             sage: len(g.min_spanning_tree())
    3444             4
    3445             sage: weight = lambda e: 1/( (e[0]+1)*(e[1]+1) )
    3446             sage: g.min_spanning_tree(weight_function=weight)
    3447             [(3, 4, {}), (2, 4, {}), (1, 4, {}), (0, 4, {})]
    3448             sage: g.min_spanning_tree(algorithm='Prim edge', starting_vertex=2, weight_function=weight)
    3449             [(2, 4, {}), (3, 4, {}), (1, 4, {}), (0, 4, {})]
    3450             sage: g.min_spanning_tree(algorithm='Prim fringe', starting_vertex=2, weight_function=weight)
    3451             [(2, 4), (4, 3), (4, 1), (4, 0)]
    3452         """
    3453         if self.is_connected()==False:
    3454             return False
    3455 
    3456         if algorithm=='Kruskal':
    3457             # Kruskal's algorithm
    3458             edges=[]
    3459             sorted_edges_iterator=iter(sorted(self.edges(), key=weight_function))
    3460             union_find = dict()
    3461             while len(edges) < self.order()-1:
    3462                 # get next edge
    3463                 e=sorted_edges_iterator.next()
    3464                 components=[]
    3465                 for start_v in e[0:2]:
    3466                     v=start_v
    3467                     children=[]
    3468 
    3469                     # Find the component a vertex lives in.
    3470                     while union_find.has_key(v):
    3471                         children.append(v)
    3472                         v=union_find[v]
    3473 
    3474                     # Compress the paths as much as we can for
    3475                     # efficiency reasons.
    3476                     for child in children:
    3477                         union_find[child]=v
    3478 
    3479                     components.append(v)
    3480 
    3481                 if components[0]!=components[1]:
    3482                     # put in edge
    3483                     edges.append(e)
    3484 
    3485                     # Union the components by making one the parent of the
    3486                     # other.
    3487                     union_find[components[0]]=components[1]
    3488 
    3489             return edges
    3490        
    3491         elif algorithm=='Prim fringe':
    3492             if starting_vertex is None:
    3493                 v = self.vertex_iterator().next()
    3494             else:
    3495                 v = starting_vertex
    3496             tree=set([v])
    3497             edges=[]
    3498 
    3499             # initialize fringe_list with v's neighbors.  fringe_list
    3500             # contains fringe_vertex: (vertex_in_tree, weight) for each
    3501             # fringe vertex
    3502 
    3503             fringe_list=dict([u,(weight_function((v,u)),v)] for u in self[v])
    3504 
    3505             cmp_fun = lambda x: fringe_list[x]
    3506 
    3507             for i in xrange(self.order()-1):
    3508                 # Find the smallest-weight fringe vertex
    3509                 u=min(fringe_list,key=cmp_fun)
    3510                 edges.append((fringe_list[u][1],u))
    3511                 tree.add(u)
    3512                 fringe_list.pop(u)
    3513 
    3514                 # Update fringe list
    3515                 for neighbor in [v for v in self[u] if v not in tree]:
    3516                     w=weight_function((u,neighbor))
    3517                     if neighbor not in fringe_list or fringe_list[neighbor][0]>w:
    3518                         fringe_list[neighbor]=(w,u)
    3519             return edges
    3520 
    3521         elif algorithm=='Prim edge':
    3522             if starting_vertex is None:
    3523                 v = self.vertex_iterator().next()
    3524             else:
    3525                 v = starting_vertex
    3526             sorted_edges=sorted(self.edges(), key=weight_function)
    3527             tree=set([v])
    3528             edges=[]
    3529 
    3530             for _ in xrange(self.order()-1):
    3531                 # Find a minimum-weight edge connecting a vertex in
    3532                 # the tree to something outside the tree.  Remove the
    3533                 # edges between tree vertices for efficiency.
    3534                 i=0
    3535                 while True:
    3536                     e = sorted_edges[i]
    3537                     v0,v1=e[0],e[1]
    3538                     if v0 in tree:
    3539                         del sorted_edges[i]
    3540                         if v1 in tree: continue
    3541                         edges.append(e)
    3542                         tree.add(v1)
    3543                         break
    3544                     elif v1 in tree:
    3545                         del sorted_edges[i]
    3546                         edges.append(e)
    3547                         tree.add(v0)
    3548                         break
    3549                     else:
    3550                         i += 1
    3551             return edges
    3552 
    3553         elif algorithm=="networkx":
    3554             import networkx
    3555             G = networkx.Graph([(u,v,dict(weight=weight_function((u,v)))) for u,v,l in self.edge_iterator()])
    3556             return list(networkx.mst(G))
    3557         else:
    3558             raise NotImplementedError, "Minimum Spanning Tree algorithm '%s' is not implemented."%algorithm
    3559 
    35603415    def modular_decomposition(self):
    35613416        r"""
    35623417        Returns the modular decomposition corresponding
  • new file sage/graphs/spanning_tree.pyx

    diff -r 4a9f7b41ae22 -r 574805b94334 sage/graphs/spanning_tree.pyx
    - +  
     1r"""
     2Spanning trees
     3
     4This module is a collection of algorithms on spanning trees. Also included in
     5the collection are algorithms for minimum spanning trees. See the book
     6[JoynerNguyenCohen2010]_ for descriptions of spanning tree algorithms,
     7including minimum spanning trees.
     8
     9**Todo**
     10
     11* Rewrite :func:`kruskal` to use priority queues. Once Cython has support
     12  for generators and the ``yield`` statement, rewrite :func:`kruskal` to use
     13  ``yield``.
     14* Prim's algorithm.
     15* Boruvka's algorithm.
     16* Parallel version of Boruvka's algorithm.
     17* Randomized spanning tree construction.
     18
     19REFERENCES:
     20
     21.. [CormenEtAl2001] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest,
     22  and Clifford Stein. *Introduction to Algorithms*. 2nd edition, The MIT Press,
     23  2001.
     24
     25.. [GoodrichTamassia2001] Michael T. Goodrich and Roberto Tamassia.
     26  *Data Structures and Algorithms in Java*. 2nd edition, John Wiley & Sons,
     27  2001.
     28
     29.. [JoynerNguyenCohen2010] David Joyner, Minh Van Nguyen, and Nathann Cohen.
     30  *Algorithmic Graph Theory*. 2010,
     31  http://code.google.com/p/graph-theory-algorithms-book/
     32
     33.. [Sahni2000] Sartaj Sahni. *Data Structures, Algorithms, and Applications
     34  in Java*. McGraw-Hill, 2000.
     35"""
     36
     37###########################################################################
     38# Copyright (c) 2007 Jason Grout <jason-sage@creativetrax.com>
     39# Copyright (c) 2009 Mike Hansen <mhansen@gmail.com>
     40# Copyright (c) 2010 Gregory McWhirter <gmcwhirt@uci.edu>
     41# Copyright (c) 2010 Minh Van Nguyen <nguyenminh2@gmail.com>
     42#
     43# This program is free software; you can redistribute it and/or modify
     44# it under the terms of the GNU General Public License as published by
     45# the Free Software Foundation; either version 2 of the License, or
     46# (at your option) any later version.
     47#
     48# This program is distributed in the hope that it will be useful,
     49# but WITHOUT ANY WARRANTY; without even the implied warranty of
     50# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     51# GNU General Public License for more details.
     52#
     53# http://www.gnu.org/licenses/
     54###########################################################################
     55
     56include "../ext/cdefs.pxi"
     57include "../ext/interrupt.pxi"
     58include "../ext/stdsage.pxi"
     59
     60cpdef kruskal(G, wfunction=None, bint check=False):
     61    r"""
     62    Minimum spanning tree using Kruskal's algorithm.
     63
     64    This function assumes that we can only compute minimum spanning trees for
     65    undirected simple graphs. Such graphs can be weighted or unweighted.
     66
     67    INPUT:
     68
     69    - ``G`` -- A graph. This can be an undirected graph, a digraph, a
     70      multigraph, or a multidigraph. Note the following behaviours:
     71
     72      - If ``G`` is unweighted, then consider the simple version of ``G``
     73        with all self-loops and multiple edges removed.
     74
     75      - If ``G`` is directed, then we only consider its undirected version.
     76
     77      - If ``G`` is weighted, we ignore all of its self-loops. Note that a
     78        weighted graph should only have numeric weights. You cannot assign
     79        numeric weights to some edges of ``G``, but have ``None`` as a
     80        weight for some other edge. If your input graph is weighted, you are
     81        responsible for assign numeric weight to each of its edges.
     82        Furthermore, we remove multiple edges as follows. First we convert
     83        ``G`` to be undirected. Suppose there are multiple edges from `u` to
     84        `v`. Among all such multiple edges, we choose one with minimum weight.
     85
     86    - ``wfunction`` -- A weight function: a function that takes an edge and
     87      returns a numeric weight. Default: ``None``. The default is to
     88      assign each edge a weight of 1.
     89
     90    - ``check`` -- Whether to first perform sanity checks on the input
     91      graph ``G``. Default: ``check=False``. If we toggle ``check=True``, the
     92      following sanity checks are first performed on ``G`` prior to running
     93      Kruskal's algorithm on that input graph:
     94
     95      - Is ``G`` the null graph?
     96      - Is ``G`` disconnected?
     97      - Is ``G`` a tree?
     98      - Is ``G`` directed?
     99      - Does ``G`` have self-loops?
     100      - Does ``G`` have multiple edges?
     101      - Is ``G`` weighted?
     102
     103      By default, we turn off the sanity checks for performance reasons. This
     104      means that by default the function assumes that its input graph is
     105      simple, connected, is not a tree, and has at least one vertex.
     106      If the input graph does not satisfy all of the latter conditions, you
     107      should set ``check=True`` to perform some sanity checks and
     108      preprocessing on the input graph. To further improve the runtime of this
     109      function, you should call it directly instead of using it indirectly
     110      via :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`.
     111
     112    OUTPUT:
     113
     114    The edges of a minimum spanning tree of ``G``, if one exists, otherwise
     115    returns the empty list.
     116
     117    - If ``G`` is a tree, return the edges of ``G`` regardless of whether
     118      ``G`` is weighted or unweighted, directed or undirected.
     119
     120    - If ``G`` is unweighted, default to using unit weight for each edge of
     121      ``G``. The default behaviour is to use the already assigned weights of
     122      ``G`` provided that ``G`` is weighted.
     123
     124    - If ``G`` is weighted and a weight function is also supplied, then use
     125      the already assigned weights of ``G``, not the weight function. If you
     126      really want to use a weight function for ``G`` even if ``G`` is
     127      weighted, first convert ``G`` to be unweighted and pass in the weight
     128      function.
     129
     130    .. seealso::
     131
     132        - :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`
     133
     134    EXAMPLES:
     135
     136    An example from pages 727--728 in [Sahni2000]_. ::
     137
     138        sage: from sage.graphs.spanning_tree import kruskal
     139        sage: G = Graph({1:{2:28, 6:10}, 2:{3:16, 7:14}, 3:{4:12}, 4:{5:22, 7:18}, 5:{6:25, 7:24}})
     140        sage: G.weighted(True)
     141        sage: E = kruskal(G, check=True); E
     142        [(1, 6, 10), (3, 4, 12), (2, 7, 14), (2, 3, 16), (4, 5, 22), (5, 6, 25)]
     143
     144    Variants of the previous example. ::
     145
     146        sage: H = Graph(G.edges(labels=False))
     147        sage: kruskal(H, check=True)
     148        [(1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None), (3, 4, None), (4, 5, None)]
     149        sage: H = DiGraph(G.edges(labels=False))
     150        sage: kruskal(H, check=True)
     151        [(1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None), (3, 4, None), (4, 5, None)]
     152        sage: G.allow_loops(True)
     153        sage: G.allow_multiple_edges(True)
     154        sage: G
     155        Looped multi-graph on 7 vertices
     156        sage: for i in range(20):
     157        ...       u = randint(1, 7)
     158        ...       v = randint(1, 7)
     159        ...       w = randint(0, 20)
     160        ...       G.add_edge(u, v, w)
     161        sage: H = copy(G)
     162        sage: H
     163        Looped multi-graph on 7 vertices
     164        sage: def sanitize(G):
     165        ...       G.allow_loops(False)
     166        ...       E = {}
     167        ...       for u, v, _ in G.multiple_edges():
     168        ...           E.setdefault(u, v)
     169        ...       for u in E:
     170        ...           W = sorted(G.edge_label(u, E[u]))
     171        ...           for w in W[1:]:
     172        ...               G.delete_edge(u, E[u], w)
     173        ...       G.allow_multiple_edges(False)
     174        sage: sanitize(H)
     175        sage: H
     176        Graph on 7 vertices
     177        sage: kruskal(G, check=True) == kruskal(H, check=True)
     178        True
     179
     180    Note that we only consider an undirected version of the input graph. Thus
     181    if ``G`` is a weighted multidigraph and ``H`` is an undirected version of
     182    ``G``, then this function should return the same minimum spanning tree
     183    for both ``G`` and ``H``. ::
     184
     185        sage: from sage.graphs.spanning_tree import kruskal
     186        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)
     187        sage: G.multiple_edges(to_undirected=False)
     188        [(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)]
     189        sage: H = G.to_undirected()
     190        sage: H.multiple_edges(to_undirected=True)
     191        [(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)]
     192        sage: kruskal(G, check=True)
     193        [(1, 2, 1), (1, 6, 10), (2, 3, 16), (2, 5, 1), (2, 7, 14), (3, 4, 3)]
     194        sage: kruskal(G, check=True) == kruskal(H, check=True)
     195        True
     196        sage: G.weighted(True)
     197        sage: H.weighted(True)
     198        sage: kruskal(G, check=True)
     199        [(1, 2, 1), (2, 5, 1), (3, 4, 3), (1, 6, 10), (2, 7, 14), (2, 3, 16)]
     200        sage: kruskal(G, check=True) == kruskal(H, check=True)
     201        True
     202
     203    An example from pages 599--601 in [GoodrichTamassia2001]_. ::
     204
     205        sage: G = Graph({"SFO":{"BOS":2704, "ORD":1846, "DFW":1464, "LAX":337},
     206        ...   "BOS":{"ORD":867, "JFK":187, "MIA":1258},
     207        ...   "ORD":{"PVD":849, "JFK":740, "BWI":621, "DFW":802},
     208        ...   "DFW":{"JFK":1391, "MIA":1121, "LAX":1235},
     209        ...   "LAX":{"MIA":2342},
     210        ...   "PVD":{"JFK":144},
     211        ...   "JFK":{"MIA":1090, "BWI":184},
     212        ...   "BWI":{"MIA":946}})
     213        sage: G.weighted(True)
     214        sage: kruskal(G, check=True)
     215        [('JFK', 'PVD', 144), ('BWI', 'JFK', 184), ('BOS', 'JFK', 187), ('LAX', 'SFO', 337), ('BWI', 'ORD', 621), ('DFW', 'ORD', 802), ('BWI', 'MIA', 946), ('DFW', 'LAX', 1235)]
     216
     217    An example from pages 568--569 in [CormenEtAl2001]_. ::
     218
     219        sage: G = Graph({"a":{"b":4, "h":8}, "b":{"c":8, "h":11},
     220        ...   "c":{"d":7, "f":4, "i":2}, "d":{"e":9, "f":14},
     221        ...   "e":{"f":10}, "f":{"g":2}, "g":{"h":1, "i":6}, "h":{"i":7}})
     222        sage: G.weighted(True)
     223        sage: kruskal(G, check=True)
     224        [('g', 'h', 1), ('c', 'i', 2), ('f', 'g', 2), ('a', 'b', 4), ('c', 'f', 4), ('c', 'd', 7), ('a', 'h', 8), ('d', 'e', 9)]
     225
     226    TESTS:
     227
     228    The input graph must not be empty. ::
     229
     230        sage: from sage.graphs.spanning_tree import kruskal
     231        sage: kruskal(graphs.EmptyGraph(), check=True)
     232        []
     233        sage: kruskal(Graph(), check=True)
     234        []
     235        sage: kruskal(Graph(multiedges=True), check=True)
     236        []
     237        sage: kruskal(Graph(loops=True), check=True)
     238        []
     239        sage: kruskal(Graph(multiedges=True, loops=True), check=True)
     240        []
     241        sage: kruskal(DiGraph(), check=True)
     242        []
     243        sage: kruskal(DiGraph(multiedges=True), check=True)
     244        []
     245        sage: kruskal(DiGraph(loops=True), check=True)
     246        []
     247        sage: kruskal(DiGraph(multiedges=True, loops=True), check=True)
     248        []
     249
     250    The input graph must be connected. ::
     251
     252        sage: def my_disconnected_graph(n, ntries, directed=False, multiedges=False, loops=False):
     253        ...       G = Graph()
     254        ...       k = randint(1, n)
     255        ...       G.add_vertices(range(k))
     256        ...       if directed:
     257        ...           G = G.to_directed()
     258        ...       if multiedges:
     259        ...           G.allow_multiple_edges(True)
     260        ...       if loops:
     261        ...           G.allow_loops(True)
     262        ...       for i in range(ntries):
     263        ...           u = randint(0, k-1)
     264        ...           v = randint(0, k-1)
     265        ...           G.add_edge(u, v)
     266        ...       while G.is_connected():
     267        ...           u = randint(0, k-1)
     268        ...           v = randint(0, k-1)
     269        ...           G.delete_edge(u, v)
     270        ...       return G
     271        sage: G = my_disconnected_graph(100, 50, directed=False, multiedges=False, loops=False)  # long time
     272        sage: kruskal(G, check=True)  # long time
     273        []
     274        sage: G = my_disconnected_graph(100, 50, directed=False, multiedges=True, loops=False)  # long time
     275        sage: kruskal(G, check=True)  # long time
     276        []
     277        sage: G = my_disconnected_graph(100, 50, directed=False, multiedges=True, loops=True)  # long time
     278        sage: kruskal(G, check=True)  # long time
     279        []
     280        sage: G = my_disconnected_graph(100, 50, directed=True, multiedges=False, loops=False)  # long time
     281        sage: kruskal(G, check=True)  # long time
     282        []
     283        sage: G = my_disconnected_graph(100, 50, directed=True, multiedges=True, loops=False)  # long time
     284        sage: kruskal(G, check=True)  # long time
     285        []
     286        sage: G = my_disconnected_graph(100, 50, directed=True, multiedges=True, loops=True)  # long time
     287        sage: kruskal(G, check=True)  # long time
     288        []
     289
     290    If the input graph is a tree, then return its edges. ::
     291
     292        sage: T = graphs.RandomTree(randint(1, 50))  # long time
     293        sage: T.edges() == kruskal(T, check=True)  # long time
     294        True
     295    """
     296    g = G
     297    sortedE_iter = None
     298    # sanity checks
     299    if check:
     300        if G.order() == 0:
     301            return []
     302        if not G.is_connected():
     303            return []
     304        # G is now assumed to be a nonempty connected graph
     305        if G.num_verts() == G.num_edges() + 1:
     306            # G is a tree
     307            return G.edges()
     308        g = G.to_undirected()
     309        g.allow_loops(False)
     310        if g.weighted():
     311            # If there are multiple edges from u to v, retain the edge of
     312            # minimum weight among all such edges.
     313            if g.allows_multiple_edges():
     314                # By this stage, g is assumed to be an undirected, weighted
     315                # multigraph. Thus when we talk about a weighted multiedge
     316                # (u, v, w) of g, we mean that (u, v, w) and (v, u, w) are
     317                # one and the same undirected multiedge having the same weight
     318                # w.
     319                # If there are multiple edges from u to v, retain only the
     320                # start and end vertices of such edges. Let a and b be the
     321                # start and end vertices, respectively, of a weighted edge
     322                # (a, b, w) having weight w. Then there are multiple weighted
     323                # edges from a to b if and only if the set uniqE has the
     324                # tuple (a, b) as an element.
     325                uniqE = set()
     326                for u, v, _ in iter(g.multiple_edges(to_undirected=True)):
     327                    uniqE.add((u, v))
     328                # Let (u, v) be an element in uniqE. Then there are multiple
     329                # weighted edges from u to v. Let W be a list of all edge
     330                # weights of multiple edges from u to v, sorted in
     331                # nondecreasing order. If w is the first element in W, then
     332                # (u, v, w) is an edge of minimum weight (there may be
     333                # several edges of minimum weight) among all weighted edges
     334                # from u to v. If i >= 2 is the i-th element in W, delete the
     335                # multiple weighted edge (u, v, i).
     336                for u, v in uniqE:
     337                    W = sorted(g.edge_label(u, v))
     338                    for w in W[1:]:
     339                        g.delete_edge(u, v, w)
     340                # all multiple edges should now be removed; check this!
     341                assert g.multiple_edges() == []
     342                g.allow_multiple_edges(False)
     343            # sort edges by weights
     344            from operator import itemgetter
     345            sortedE_iter = iter(sorted(g.edges(), key=itemgetter(2)))
     346        else:
     347            g = g.to_simple()
     348            if wfunction is None:
     349                sortedE_iter = iter(sorted(g.edges()))
     350            else:
     351                sortedE_iter = iter(sorted(g.edges(), key=wfunction))
     352    # G is assumed to be simple, undirected, and unweighted
     353    else:
     354        if wfunction is None:
     355            sortedE_iter = iter(sorted(g.edges()))
     356        else:
     357            sortedE_iter = iter(sorted(g.edges(), key=wfunction))
     358    # Kruskal's algorithm
     359    T = []
     360    cdef int n = g.order()
     361    cdef int m = n - 1
     362    cdef int i = 0  # count the number of edges added so far
     363    union_find = dict()
     364    while i < m:
     365        e = sortedE_iter.next()
     366        components = []
     367        # acyclic test via union-find
     368        for startv in iter(e[0:2]):
     369            v = startv
     370            children = []
     371            # find the component a vertex lives in
     372            while v in union_find:
     373                children.append(v)
     374                v = union_find[v]
     375            # compress the paths as much as we can for efficiency reasons
     376            for c in children:
     377                union_find[c] = v
     378            components.append(v)
     379        if components[0] != components[1]:
     380            i += 1
     381            # NOTE: Once Cython supports generator and the yield statement,
     382            # we should replace the following line with a yield statement.
     383            # That way, we could access the edge of a minimum spanning tree
     384            # immediately after it is found, instead of waiting for all the
     385            # edges to be found and return the edges as a list.
     386            T.append(e)
     387            # union the components by making one the parent of the other
     388            union_find[components[0]] = components[1]
     389    return T