# 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/doc/en/reference/graphs.rst	Thu Jan 13 22:03:50 2011 +0000
+++ b/doc/en/reference/graphs.rst	Tue Dec 07 19:36:47 2010 -0800
@@ -44,6 +44,7 @@
 
    sage/graphs/graph_coloring
    sage/graphs/cliquer
+   sage/graphs/spanning_tree
    sage/graphs/pq_trees
    sage/graphs/matchpoly
    sage/graphs/graph_latex
diff -r 4a9f7b41ae22 -r 574805b94334 module_list.py
--- a/module_list.py	Thu Jan 13 22:03:50 2011 +0000
+++ b/module_list.py	Tue Dec 07 19:36:47 2010 -0800
@@ -380,6 +380,9 @@
                          'sage/graphs/planarity/platformTime.h',
                          'sage/graphs/planarity/stack.h']),
 
+    Extension('sage.graphs.spanning_tree',
+              sources = ['sage/graphs/spanning_tree.pyx']),
+
     Extension('sage.graphs.trees',
               sources = ['sage/graphs/trees.pyx']),
 
diff -r 4a9f7b41ae22 -r 574805b94334 sage/graphs/generic_graph.py
--- a/sage/graphs/generic_graph.py	Thu Jan 13 22:03:50 2011 +0000
+++ b/sage/graphs/generic_graph.py	Tue Dec 07 19:36:47 2010 -0800
@@ -2029,6 +2029,142 @@
                 else:
                     return d
 
+    def min_spanning_tree(self,
+                          weight_function=lambda e: 1,
+                          algorithm="Kruskal",
+                          starting_vertex=None,
+                          check=False):
+        r"""
+        Returns the edges of a minimum spanning tree.
+
+        INPUT:
+
+        - ``weight_function`` -- A function that takes an edge and returns a
+          numeric weight. Defaults to assigning each edge a weight of 1.
+
+        - ``algorithm`` -- The algorithm to use in computing a minimum sapnning
+          tree of ``G``. The default is to use Kruskal's algorithm. The
+          following algorithms are supported:
+
+          - ``"Kruskal"`` -- Kruskal's algorithm.
+
+          - ``"Prim_fringe"`` -- a variant of Prim's algorithm.
+            ``"Prim_fringe"`` ignores the labels on the edges.
+
+          - ``"Prim_edge"`` -- a variant of Prim's algorithm.
+
+          - ``NetworkX`` -- Uses NetworkX's minimum spanning tree
+            implementation.
+
+        - ``starting_vertex`` -- The vertex from which to begin the search
+          for a minimum spanning tree.
+
+        - ``check`` -- Boolean; default: ``False``. Whether to first perform
+          sanity checks on the input graph ``G``. If appropriate, ``check``
+          is passed on to any minimum spanning tree functions that are
+          invoked from the current method. See the documentation of the
+          corresponding functions for details on what sort of sanity checks
+          will be performed.
+
+        OUTPUT:
+
+        The edges of a minimum spanning tree of ``G``, if one exists, otherwise
+        returns the empty list.
+
+        .. seealso::
+
+            - :func:`sage.graphs.spanning_tree.kruskal`
+
+        EXAMPLES:
+
+        Kruskal's algorithm::
+
+            sage: g = graphs.CompleteGraph(5)
+            sage: len(g.min_spanning_tree())
+            4
+            sage: weight = lambda e: 1 / ((e[0] + 1) * (e[1] + 1))
+            sage: g.min_spanning_tree(weight_function=weight)
+            [(3, 4, {}), (2, 4, {}), (1, 4, {}), (0, 4, {})]
+            sage: g = graphs.PetersenGraph()
+            sage: g.allow_multiple_edges(True)
+            sage: g.weighted(True)
+            sage: g.add_edges(g.edges())
+            sage: g.min_spanning_tree()
+            [(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)]
+
+        Prim's algorithm::
+
+            sage: g = graphs.CompleteGraph(5)
+            sage: g.min_spanning_tree(algorithm='Prim_edge', starting_vertex=2, weight_function=weight)
+            [(2, 4, {}), (3, 4, {}), (1, 4, {}), (0, 4, {})]
+            sage: g.min_spanning_tree(algorithm='Prim_fringe', starting_vertex=2, weight_function=weight)
+            [(2, 4), (4, 3), (4, 1), (4, 0)]
+        """
+        if algorithm == "Kruskal":
+            from spanning_tree import kruskal
+            return kruskal(self, wfunction=weight_function, check=check)
+        elif algorithm == "Prim_fringe":
+            if starting_vertex is None:
+                v = self.vertex_iterator().next()
+            else:
+                v = starting_vertex
+            tree = set([v])
+            edges = []
+            # Initialize fringe_list with v's neighbors. Fringe_list
+            # contains fringe_vertex: (vertex_in_tree, weight) for each
+            # fringe vertex.
+            fringe_list = dict([u, (weight_function((v, u)), v)] for u in self[v])
+            cmp_fun = lambda x: fringe_list[x]
+            for i in range(self.order() - 1):
+                # find the smallest-weight fringe vertex
+                u = min(fringe_list, key=cmp_fun)
+                edges.append((fringe_list[u][1], u))
+                tree.add(u)
+                fringe_list.pop(u)
+                # update fringe list
+                for neighbor in [v for v in self[u] if v not in tree]:
+                    w = weight_function((u, neighbor))
+                    if neighbor not in fringe_list or fringe_list[neighbor][0] > w:
+                        fringe_list[neighbor] = (w, u)
+            return edges
+        elif algorithm == "Prim_edge":
+            if starting_vertex is None:
+                v = self.vertex_iterator().next()
+            else:
+                v = starting_vertex
+            sorted_edges = sorted(self.edges(), key=weight_function)
+            tree = set([v])
+            edges = []
+            for _ in range(self.order() - 1):
+                # Find a minimum-weight edge connecting a vertex in the tree
+                # to something outside the tree. Remove the edges between
+                # tree vertices for efficiency.
+                i = 0
+                while True:
+                    e = sorted_edges[i]
+                    v0, v1 = e[0], e[1]
+                    if v0 in tree:
+                        del sorted_edges[i]
+                        if v1 in tree:
+                            continue
+                        edges.append(e)
+                        tree.add(v1)
+                        break
+                    elif v1 in tree:
+                        del sorted_edges[i]
+                        edges.append(e)
+                        tree.add(v0)
+                        break
+                    else:
+                        i += 1
+            return edges
+        elif algorithm == "NetworkX":
+            import networkx
+            G = networkx.Graph([(u, v, dict(weight=weight_function((u, v)))) for u, v, l in self.edge_iterator()])
+            return list(networkx.mst(G))
+        else:
+            raise NotImplementedError("Minimum Spanning Tree algorithm '%s' is not implemented." % algorithm)
+
     def spanning_trees_count(self, root_vertex=None):
         """
         Returns the number of spanning trees in a graph. In the case of a
diff -r 4a9f7b41ae22 -r 574805b94334 sage/graphs/graph.py
--- a/sage/graphs/graph.py	Thu Jan 13 22:03:50 2011 +0000
+++ b/sage/graphs/graph.py	Tue Dec 07 19:36:47 2010 -0800
@@ -3412,151 +3412,6 @@
 
     ### Miscellaneous
 
-    def min_spanning_tree(self, weight_function=lambda e: 1,
-                          algorithm='Kruskal',
-                          starting_vertex=None ):
-        """
-        Returns the edges of a minimum spanning tree, if one exists,
-        otherwise returns False.
-        
-        INPUT:
-        
-        
-        -  ``weight_function`` - A function that takes an edge
-           and returns a numeric weight. Defaults to assigning each edge a
-           weight of 1.
-        
-        -  ``algorithm`` - Three variants of algorithms are
-           implemented: 'Kruskal', 'Prim fringe', and 'Prim edge' (the last
-           two are variants of Prim's algorithm). Defaults to 'Kruskal'.
-           Currently, 'Prim fringe' ignores the labels on the edges.
-        
-        -  ``starting_vertex`` - The vertex with which to
-           start Prim's algorithm.
-        
-        
-        OUTPUT: the edges of a minimum spanning tree.
-        
-        EXAMPLES::
-        
-            sage: g=graphs.CompleteGraph(5)
-            sage: len(g.min_spanning_tree())
-            4
-            sage: weight = lambda e: 1/( (e[0]+1)*(e[1]+1) )
-            sage: g.min_spanning_tree(weight_function=weight)
-            [(3, 4, {}), (2, 4, {}), (1, 4, {}), (0, 4, {})]
-            sage: g.min_spanning_tree(algorithm='Prim edge', starting_vertex=2, weight_function=weight)
-            [(2, 4, {}), (3, 4, {}), (1, 4, {}), (0, 4, {})]
-            sage: g.min_spanning_tree(algorithm='Prim fringe', starting_vertex=2, weight_function=weight)
-            [(2, 4), (4, 3), (4, 1), (4, 0)]
-        """
-        if self.is_connected()==False:
-            return False
-
-        if algorithm=='Kruskal':
-            # Kruskal's algorithm
-            edges=[]
-            sorted_edges_iterator=iter(sorted(self.edges(), key=weight_function))
-            union_find = dict()
-            while len(edges) < self.order()-1:
-                # get next edge
-                e=sorted_edges_iterator.next()
-                components=[]
-                for start_v in e[0:2]:
-                    v=start_v
-                    children=[]
-
-                    # Find the component a vertex lives in.
-                    while union_find.has_key(v):
-                        children.append(v)
-                        v=union_find[v]
-
-                    # Compress the paths as much as we can for
-                    # efficiency reasons.
-                    for child in children:
-                        union_find[child]=v
-
-                    components.append(v)
-
-                if components[0]!=components[1]:
-                    # put in edge
-                    edges.append(e)
-
-                    # Union the components by making one the parent of the
-                    # other.
-                    union_find[components[0]]=components[1]
-
-            return edges
-        
-        elif algorithm=='Prim fringe':
-            if starting_vertex is None:
-                v = self.vertex_iterator().next()
-            else:
-                v = starting_vertex
-            tree=set([v])
-            edges=[]
-
-            # initialize fringe_list with v's neighbors.  fringe_list
-            # contains fringe_vertex: (vertex_in_tree, weight) for each
-            # fringe vertex
-
-            fringe_list=dict([u,(weight_function((v,u)),v)] for u in self[v])
-
-            cmp_fun = lambda x: fringe_list[x]
-
-            for i in xrange(self.order()-1):
-                # Find the smallest-weight fringe vertex
-                u=min(fringe_list,key=cmp_fun)
-                edges.append((fringe_list[u][1],u))
-                tree.add(u)
-                fringe_list.pop(u)
-
-                # Update fringe list
-                for neighbor in [v for v in self[u] if v not in tree]:
-                    w=weight_function((u,neighbor))
-                    if neighbor not in fringe_list or fringe_list[neighbor][0]>w:
-                        fringe_list[neighbor]=(w,u)
-            return edges
-
-        elif algorithm=='Prim edge':
-            if starting_vertex is None:
-                v = self.vertex_iterator().next()
-            else:
-                v = starting_vertex
-            sorted_edges=sorted(self.edges(), key=weight_function)
-            tree=set([v])
-            edges=[]
-
-            for _ in xrange(self.order()-1):
-                # Find a minimum-weight edge connecting a vertex in
-                # the tree to something outside the tree.  Remove the
-                # edges between tree vertices for efficiency.
-                i=0
-                while True:
-                    e = sorted_edges[i]
-                    v0,v1=e[0],e[1]
-                    if v0 in tree:
-                        del sorted_edges[i]
-                        if v1 in tree: continue
-                        edges.append(e)
-                        tree.add(v1)
-                        break
-                    elif v1 in tree:
-                        del sorted_edges[i]
-                        edges.append(e)
-                        tree.add(v0)
-                        break
-                    else:
-                        i += 1
-            return edges
-
-        elif algorithm=="networkx":
-            import networkx
-            G = networkx.Graph([(u,v,dict(weight=weight_function((u,v)))) for u,v,l in self.edge_iterator()])
-            return list(networkx.mst(G))
-        else:
-            raise NotImplementedError, "Minimum Spanning Tree algorithm '%s' is not implemented."%algorithm
-
     def modular_decomposition(self):
         r"""
         Returns the modular decomposition corresponding
diff -r 4a9f7b41ae22 -r 574805b94334 sage/graphs/spanning_tree.pyx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sage/graphs/spanning_tree.pyx	Tue Dec 07 19:36:47 2010 -0800
@@ -0,0 +1,389 @@
+r"""
+Spanning trees
+
+This module is a collection of algorithms on spanning trees. Also included in
+the collection are algorithms for minimum spanning trees. See the book
+[JoynerNguyenCohen2010]_ for descriptions of spanning tree algorithms,
+including minimum spanning trees.
+
+**Todo**
+
+* Rewrite :func:`kruskal` to use priority queues. Once Cython has support
+  for generators and the ``yield`` statement, rewrite :func:`kruskal` to use
+  ``yield``.
+* Prim's algorithm.
+* Boruvka's algorithm.
+* Parallel version of Boruvka's algorithm.
+* Randomized spanning tree construction.
+
+REFERENCES:
+
+.. [CormenEtAl2001] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest,
+  and Clifford Stein. *Introduction to Algorithms*. 2nd edition, The MIT Press,
+  2001.
+
+.. [GoodrichTamassia2001] Michael T. Goodrich and Roberto Tamassia.
+  *Data Structures and Algorithms in Java*. 2nd edition, John Wiley & Sons,
+  2001.
+
+.. [JoynerNguyenCohen2010] David Joyner, Minh Van Nguyen, and Nathann Cohen.
+  *Algorithmic Graph Theory*. 2010,
+  http://code.google.com/p/graph-theory-algorithms-book/
+
+.. [Sahni2000] Sartaj Sahni. *Data Structures, Algorithms, and Applications
+  in Java*. McGraw-Hill, 2000.
+"""
+
+###########################################################################
+# Copyright (c) 2007 Jason Grout <jason-sage@creativetrax.com>
+# Copyright (c) 2009 Mike Hansen <mhansen@gmail.com>
+# Copyright (c) 2010 Gregory McWhirter <gmcwhirt@uci.edu>
+# Copyright (c) 2010 Minh Van Nguyen <nguyenminh2@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# http://www.gnu.org/licenses/
+###########################################################################
+
+include "../ext/cdefs.pxi"
+include "../ext/interrupt.pxi"
+include "../ext/stdsage.pxi"
+
+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.
+
+    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, we ignore all of its self-loops. Note that a
+        weighted 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.
+
+    - ``wfunction`` -- A weight function: a function that takes an edge and
+      returns a numeric weight. Default: ``None``. The default is to
+      assign each edge a weight of 1.
+
+    - ``check`` -- Whether to first perform sanity checks on the input
+      graph ``G``. Default: ``check=False``. If we toggle ``check=True``, the
+      following sanity checks are first performed on ``G`` prior to running
+      Kruskal's algorithm on that input graph:
+
+      - 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?
+      - Is ``G`` weighted?
+
+      By default, we turn off the sanity checks for performance reasons. This
+      means that by default the function assumes that its input graph is
+      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`.
+
+    OUTPUT:
+
+    The edges of a minimum spanning tree of ``G``, if one exists, otherwise
+    returns the empty list.
+
+    - If ``G`` is a tree, return the edges of ``G`` regardless of whether
+      ``G`` is weighted or unweighted, directed or undirected.
+
+    - If ``G`` is unweighted, default to using unit weight for each edge of
+      ``G``. The default behaviour is to use the already assigned weights of
+      ``G`` provided that ``G`` is weighted.
+
+    - If ``G`` is weighted and a weight function is also supplied, then use
+      the already assigned weights of ``G``, not the weight function. If you
+      really want to use a weight function for ``G`` even if ``G`` is
+      weighted, first convert ``G`` to be unweighted and pass in the weight
+      function.
+
+    .. seealso::
+
+        - :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`
+
+    EXAMPLES:
+
+    An example from pages 727--728 in [Sahni2000]_. ::
+
+        sage: from sage.graphs.spanning_tree import kruskal
+        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}})
+        sage: G.weighted(True)
+        sage: E = kruskal(G, check=True); E
+        [(1, 6, 10), (3, 4, 12), (2, 7, 14), (2, 3, 16), (4, 5, 22), (5, 6, 25)]
+
+    Variants of the previous example. ::
+
+        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
+        Looped multi-graph on 7 vertices
+        sage: for i in range(20):
+        ...       u = randint(1, 7)
+        ...       v = randint(1, 7)
+        ...       w = randint(0, 20)
+        ...       G.add_edge(u, v, w)
+        sage: H = copy(G)
+        sage: H
+        Looped multi-graph on 7 vertices
+        sage: def sanitize(G):
+        ...       G.allow_loops(False)
+        ...       E = {}
+        ...       for u, v, _ in G.multiple_edges():
+        ...           E.setdefault(u, v)
+        ...       for u in E:
+        ...           W = sorted(G.edge_label(u, E[u]))
+        ...           for w in W[1:]:
+        ...               G.delete_edge(u, E[u], w)
+        ...       G.allow_multiple_edges(False)
+        sage: sanitize(H)
+        sage: H
+        Graph on 7 vertices
+        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), (2, 5, 1), (3, 4, 3), (1, 6, 10), (2, 7, 14), (2, 3, 16)]
+        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},
+        ...   "BOS":{"ORD":867, "JFK":187, "MIA":1258},
+        ...   "ORD":{"PVD":849, "JFK":740, "BWI":621, "DFW":802},
+        ...   "DFW":{"JFK":1391, "MIA":1121, "LAX":1235},
+        ...   "LAX":{"MIA":2342},
+        ...   "PVD":{"JFK":144},
+        ...   "JFK":{"MIA":1090, "BWI":184},
+        ...   "BWI":{"MIA":946}})
+        sage: G.weighted(True)
+        sage: kruskal(G, check=True)
+        [('JFK', 'PVD', 144), ('BWI', 'JFK', 184), ('BOS', 'JFK', 187), ('LAX', 'SFO', 337), ('BWI', 'ORD', 621), ('DFW', 'ORD', 802), ('BWI', 'MIA', 946), ('DFW', 'LAX', 1235)]
+
+    An example from pages 568--569 in [CormenEtAl2001]_. ::
+
+        sage: G = Graph({"a":{"b":4, "h":8}, "b":{"c":8, "h":11},
+        ...   "c":{"d":7, "f":4, "i":2}, "d":{"e":9, "f":14},
+        ...   "e":{"f":10}, "f":{"g":2}, "g":{"h":1, "i":6}, "h":{"i":7}})
+        sage: G.weighted(True)
+        sage: kruskal(G, check=True)
+        [('g', 'h', 1), ('c', 'i', 2), ('f', 'g', 2), ('a', 'b', 4), ('c', 'f', 4), ('c', 'd', 7), ('a', 'h', 8), ('d', 'e', 9)]
+
+    TESTS:
+
+    The input graph must not be empty. ::
+
+        sage: from sage.graphs.spanning_tree import kruskal
+        sage: kruskal(graphs.EmptyGraph(), check=True)
+        []
+        sage: kruskal(Graph(), check=True)
+        []
+        sage: kruskal(Graph(multiedges=True), check=True)
+        []
+        sage: kruskal(Graph(loops=True), check=True)
+        []
+        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. ::
+
+        sage: def my_disconnected_graph(n, ntries, directed=False, multiedges=False, loops=False):
+        ...       G = Graph()
+        ...       k = randint(1, n)
+        ...       G.add_vertices(range(k))
+        ...       if directed:
+        ...           G = G.to_directed()
+        ...       if multiedges:
+        ...           G.allow_multiple_edges(True)
+        ...       if loops:
+        ...           G.allow_loops(True)
+        ...       for i in range(ntries):
+        ...           u = randint(0, k-1)
+        ...           v = randint(0, k-1)
+        ...           G.add_edge(u, v)
+        ...       while G.is_connected():
+        ...           u = randint(0, k-1)
+        ...           v = randint(0, k-1)
+        ...           G.delete_edge(u, v)
+        ...       return G
+        sage: G = my_disconnected_graph(100, 50, directed=False, multiedges=False, loops=False)  # long time
+        sage: kruskal(G, check=True)  # long time
+        []
+        sage: G = my_disconnected_graph(100, 50, directed=False, multiedges=True, loops=False)  # long time
+        sage: kruskal(G, check=True)  # long time
+        []
+        sage: G = my_disconnected_graph(100, 50, directed=False, multiedges=True, loops=True)  # long time
+        sage: kruskal(G, check=True)  # long time
+        []
+        sage: G = my_disconnected_graph(100, 50, directed=True, multiedges=False, loops=False)  # long time
+        sage: kruskal(G, check=True)  # long time
+        []
+        sage: G = my_disconnected_graph(100, 50, directed=True, multiedges=True, loops=False)  # long time
+        sage: kruskal(G, check=True)  # long time
+        []
+        sage: G = my_disconnected_graph(100, 50, directed=True, multiedges=True, loops=True)  # long time
+        sage: kruskal(G, check=True)  # long time
+        []
+
+    If the input graph is a tree, then return its edges. ::
+
+        sage: T = graphs.RandomTree(randint(1, 50))  # long time
+        sage: T.edges() == kruskal(T, check=True)  # long time
+        True
+    """
+    g = G
+    sortedE_iter = None
+    # sanity checks
+    if check:
+        if G.order() == 0:
+            return []
+        if not G.is_connected():
+            return []
+        # G is now assumed to be a nonempty connected graph
+        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():
+            # 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)
+            # sort edges by weights
+            from operator import itemgetter
+            sortedE_iter = iter(sorted(g.edges(), key=itemgetter(2)))
+        else:
+            g = g.to_simple()
+            if wfunction is None:
+                sortedE_iter = iter(sorted(g.edges()))
+            else:
+                sortedE_iter = iter(sorted(g.edges(), key=wfunction))
+    # G is assumed to be simple, undirected, and unweighted
+    else:
+        if wfunction is None:
+            sortedE_iter = iter(sorted(g.edges()))
+        else:
+            sortedE_iter = iter(sorted(g.edges(), key=wfunction))
+    # Kruskal's algorithm
+    T = []
+    cdef int n = g.order()
+    cdef int m = n - 1
+    cdef int i = 0  # count the number of edges added so far
+    union_find = dict()
+    while i < m:
+        e = sortedE_iter.next()
+        components = []
+        # acyclic test via union-find
+        for startv in iter(e[0:2]):
+            v = startv
+            children = []
+            # find the component a vertex lives in
+            while v in union_find:
+                children.append(v)
+                v = union_find[v]
+            # compress the paths as much as we can for efficiency reasons
+            for c in children:
+                union_find[c] = v
+            components.append(v)
+        if components[0] != components[1]:
+            i += 1
+            # NOTE: Once Cython supports generator and the yield statement,
+            # we should replace the following line with a yield statement.
+            # That way, we could access the edge of a minimum spanning tree
+            # immediately after it is found, instead of waiting for all the
+            # edges to be found and return the edges as a list.
+            T.append(e)
+            # union the components by making one the parent of the other
+            union_find[components[0]] = components[1]
+    return T
