Ticket #10433: trac-10433_clean-up-kruskal.patch

File trac-10433_clean-up-kruskal.patch, 31.0 KB (added by mvngu, 9 years ago)
  • doc/en/reference/graphs.rst

    # HG changeset patch
    # User Minh Van Nguyen <nguyenminh2@gmail.com>
    # Date 1291779407 28800
    # Node ID 3d6131c2d38804f643598629df4ec88047259523
    # Parent  777e700394384024109c7f3114b68d1e6d810554
    #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 --git a/doc/en/reference/graphs.rst b/doc/en/reference/graphs.rst
    a b  
    4343
    4444   sage/graphs/graph_coloring
    4545   sage/graphs/cliquer
     46   sage/graphs/spanning_tree
    4647   sage/graphs/pq_trees
    4748   sage/graphs/graph_latex
    4849   sage/graphs/graph_list
  • module_list.py

    diff --git a/module_list.py b/module_list.py
    a b  
    368368                         'sage/graphs/planarity/platformTime.h',
    369369                         'sage/graphs/planarity/stack.h']),
    370370
     371    Extension('sage.graphs.spanning_tree',
     372              sources = ['sage/graphs/spanning_tree.pyx']),
     373
    371374    Extension('sage.graphs.trees',
    372375              sources = ['sage/graphs/trees.pyx']),
    373376
  • sage/graphs/generic_graph.py

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

    diff --git a/sage/graphs/graph.py b/sage/graphs/graph.py
    a b  
    33433343
    33443344    ### Miscellaneous
    33453345
    3346     def min_spanning_tree(self, weight_function=lambda e: 1,
    3347                           algorithm='Kruskal',
    3348                           starting_vertex=None ):
    3349         """
    3350         Returns the edges of a minimum spanning tree, if one exists,
    3351         otherwise returns False.
    3352        
    3353         INPUT:
    3354        
    3355        
    3356         -  ``weight_function`` - A function that takes an edge
    3357            and returns a numeric weight. Defaults to assigning each edge a
    3358            weight of 1.
    3359        
    3360         -  ``algorithm`` - Three variants of algorithms are
    3361            implemented: 'Kruskal', 'Prim fringe', and 'Prim edge' (the last
    3362            two are variants of Prim's algorithm). Defaults to 'Kruskal'.
    3363            Currently, 'Prim fringe' ignores the labels on the edges.
    3364        
    3365         -  ``starting_vertex`` - The vertex with which to
    3366            start Prim's algorithm.
    3367        
    3368        
    3369         OUTPUT: the edges of a minimum spanning tree.
    3370        
    3371         EXAMPLES::
    3372        
    3373             sage: g=graphs.CompleteGraph(5)
    3374             sage: len(g.min_spanning_tree())
    3375             4
    3376             sage: weight = lambda e: 1/( (e[0]+1)*(e[1]+1) )
    3377             sage: g.min_spanning_tree(weight_function=weight)
    3378             [(3, 4, {}), (2, 4, {}), (1, 4, {}), (0, 4, {})]
    3379             sage: g.min_spanning_tree(algorithm='Prim edge', starting_vertex=2, weight_function=weight)
    3380             [(2, 4, {}), (3, 4, {}), (1, 4, {}), (0, 4, {})]
    3381             sage: g.min_spanning_tree(algorithm='Prim fringe', starting_vertex=2, weight_function=weight)
    3382             [(2, 4), (4, 3), (4, 1), (4, 0)]
    3383         """
    3384         if self.is_connected()==False:
    3385             return False
    3386 
    3387         if algorithm=='Kruskal':
    3388             # Kruskal's algorithm
    3389             edges=[]
    3390             sorted_edges_iterator=iter(sorted(self.edges(), key=weight_function))
    3391             union_find = dict()
    3392             while len(edges) < self.order()-1:
    3393                 # get next edge
    3394                 e=sorted_edges_iterator.next()
    3395                 components=[]
    3396                 for start_v in e[0:2]:
    3397                     v=start_v
    3398                     children=[]
    3399 
    3400                     # Find the component a vertex lives in.
    3401                     while union_find.has_key(v):
    3402                         children.append(v)
    3403                         v=union_find[v]
    3404 
    3405                     # Compress the paths as much as we can for
    3406                     # efficiency reasons.
    3407                     for child in children:
    3408                         union_find[child]=v
    3409 
    3410                     components.append(v)
    3411 
    3412                 if components[0]!=components[1]:
    3413                     # put in edge
    3414                     edges.append(e)
    3415 
    3416                     # Union the components by making one the parent of the
    3417                     # other.
    3418                     union_find[components[0]]=components[1]
    3419 
    3420             return edges
    3421        
    3422         elif algorithm=='Prim fringe':
    3423             if starting_vertex is None:
    3424                 v = self.vertex_iterator().next()
    3425             else:
    3426                 v = starting_vertex
    3427             tree=set([v])
    3428             edges=[]
    3429 
    3430             # initialize fringe_list with v's neighbors.  fringe_list
    3431             # contains fringe_vertex: (vertex_in_tree, weight) for each
    3432             # fringe vertex
    3433 
    3434             fringe_list=dict([u,(weight_function((v,u)),v)] for u in self[v])
    3435 
    3436             cmp_fun = lambda x: fringe_list[x]
    3437 
    3438             for i in xrange(self.order()-1):
    3439                 # Find the smallest-weight fringe vertex
    3440                 u=min(fringe_list,key=cmp_fun)
    3441                 edges.append((fringe_list[u][1],u))
    3442                 tree.add(u)
    3443                 fringe_list.pop(u)
    3444 
    3445                 # Update fringe list
    3446                 for neighbor in [v for v in self[u] if v not in tree]:
    3447                     w=weight_function((u,neighbor))
    3448                     if neighbor not in fringe_list or fringe_list[neighbor][0]>w:
    3449                         fringe_list[neighbor]=(w,u)
    3450             return edges
    3451 
    3452         elif algorithm=='Prim edge':
    3453             if starting_vertex is None:
    3454                 v = self.vertex_iterator().next()
    3455             else:
    3456                 v = starting_vertex
    3457             sorted_edges=sorted(self.edges(), key=weight_function)
    3458             tree=set([v])
    3459             edges=[]
    3460 
    3461             for _ in xrange(self.order()-1):
    3462                 # Find a minimum-weight edge connecting a vertex in
    3463                 # the tree to something outside the tree.  Remove the
    3464                 # edges between tree vertices for efficiency.
    3465                 i=0
    3466                 while True:
    3467                     e = sorted_edges[i]
    3468                     v0,v1=e[0],e[1]
    3469                     if v0 in tree:
    3470                         del sorted_edges[i]
    3471                         if v1 in tree: continue
    3472                         edges.append(e)
    3473                         tree.add(v1)
    3474                         break
    3475                     elif v1 in tree:
    3476                         del sorted_edges[i]
    3477                         edges.append(e)
    3478                         tree.add(v0)
    3479                         break
    3480                     else:
    3481                         i += 1
    3482             return edges
    3483 
    3484         elif algorithm=="networkx":
    3485             import networkx
    3486             G = networkx.Graph([(u,v,dict(weight=weight_function((u,v)))) for u,v,l in self.edge_iterator()])
    3487             return list(networkx.mst(G))
    3488         else:
    3489             raise NotImplementedError, "Minimum Spanning Tree algorithm '%s' is not implemented."%algorithm
    3490 
    34913346    def modular_decomposition(self):
    34923347        r"""
    34933348        Returns the modular decomposition corresponding
  • new file sage/graphs/spanning_tree.pyx

    diff --git a/sage/graphs/spanning_tree.pyx b/sage/graphs/spanning_tree.pyx
    new file mode 100644
    - +  
     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
  • sage/misc/sagedoc.py

    diff --git a/sage/misc/sagedoc.py b/sage/misc/sagedoc.py
    a b  
    890890
    891891        sage: len(search_doc('tree', interact=False).splitlines()) > 2000
    892892        True
    893         sage: len(search_doc('tree', whole_word=True, interact=False).splitlines()) < 200
     893        sage: len(search_doc('tree', whole_word=True, interact=False).splitlines()) < 300
    894894        True
    895895    """
    896896    return _search_src_or_doc('doc', string, extra1=extra1, extra2=extra2,