passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2723 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,1457 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # cython: binding=True
3
+ r"""
4
+ Spanning trees
5
+
6
+ This module is a collection of algorithms on spanning trees. Also included in
7
+ the collection are algorithms for minimum spanning trees. See the book
8
+ [JNC2010]_ for descriptions of spanning tree algorithms,
9
+ including minimum spanning trees.
10
+
11
+ .. SEEALSO::
12
+
13
+ - :meth:`GenericGraph.min_spanning_tree
14
+ <sage.graphs.generic_graph.GenericGraph.min_spanning_tree>`.
15
+
16
+ .. TODO::
17
+
18
+ - Parallel version of Boruvka's algorithm.
19
+
20
+
21
+ Methods
22
+ -------
23
+ """
24
+
25
+ # ****************************************************************************
26
+ # Copyright (c) 2007 Jason Grout <jason-sage@creativetrax.com>
27
+ # Copyright (c) 2009 Mike Hansen <mhansen@gmail.com>
28
+ # Copyright (c) 2010 Gregory McWhirter <gmcwhirt@uci.edu>
29
+ # Copyright (c) 2010 Minh Van Nguyen <nguyenminh2@gmail.com>
30
+ #
31
+ # This program is free software: you can redistribute it and/or modify
32
+ # it under the terms of the GNU General Public License as published by
33
+ # the Free Software Foundation, either version 2 of the License, or
34
+ # (at your option) any later version.
35
+ # https://www.gnu.org/licenses/
36
+ # ****************************************************************************
37
+
38
+ from memory_allocator cimport MemoryAllocator
39
+ from sage.sets.disjoint_set cimport DisjointSet_of_hashables
40
+
41
+
42
+ def kruskal(G, by_weight=True, weight_function=None, check_weight=False, check=False):
43
+ r"""
44
+ Minimum spanning tree using Kruskal's algorithm.
45
+
46
+ This function assumes that we can only compute minimum spanning trees for
47
+ undirected graphs. Such graphs can be weighted or unweighted, and they can
48
+ have multiple edges (since we are computing the minimum spanning tree, only
49
+ the minimum weight among all `(u,v)`-edges is considered, for each pair
50
+ of vertices `u`, `v`).
51
+
52
+ INPUT:
53
+
54
+ - ``G`` -- an undirected graph
55
+
56
+ - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in
57
+ the graph are weighted. If ``False``, all edges have weight 1.
58
+
59
+ - ``weight_function`` -- function (default: ``None``); a function that takes
60
+ as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``,
61
+ ``by_weight`` is automatically set to ``True``. If ``None`` and
62
+ ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not
63
+ ``None``, else ``1`` as a weight.
64
+
65
+ - ``check_weight`` -- boolean (default: ``False``); whether to check that
66
+ the ``weight_function`` outputs a number for each edge
67
+
68
+ - ``check`` -- boolean (default: ``False``); whether to first perform sanity
69
+ checks on the input graph ``G``. Default: ``check=False``. If we toggle
70
+ ``check=True``, the following sanity checks are first performed on ``G``
71
+ prior to running Kruskal's algorithm on that input graph:
72
+
73
+ - Is ``G`` the null graph?
74
+ - Is ``G`` disconnected?
75
+ - Is ``G`` a tree?
76
+ - Does ``G`` have self-loops?
77
+ - Does ``G`` have multiple edges?
78
+
79
+ By default, we turn off the sanity checks for performance reasons. This
80
+ means that by default the function assumes that its input graph is
81
+ connected, and has at least one vertex. Otherwise, you should set
82
+ ``check=True`` to perform some sanity checks and preprocessing on the
83
+ input graph. If ``G`` has multiple edges or self-loops, the algorithm
84
+ still works, but the running-time can be improved if these edges are
85
+ removed. To further improve the runtime of this function, you should call
86
+ it directly instead of using it indirectly via
87
+ :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`.
88
+
89
+ OUTPUT:
90
+
91
+ The edges of a minimum spanning tree of ``G``, if one exists, otherwise
92
+ returns the empty list.
93
+
94
+ .. SEEALSO::
95
+
96
+ - :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`
97
+ - :func:`kruskal_iterator`
98
+ - :func:`filter_kruskal` and :func:`filter_kruskal_iterator`
99
+
100
+ EXAMPLES:
101
+
102
+ An example from pages 727--728 in [Sah2000]_. ::
103
+
104
+ sage: from sage.graphs.spanning_tree import kruskal
105
+ 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}})
106
+ sage: G.weighted(True)
107
+ sage: E = kruskal(G, check=True); E
108
+ [(1, 6, 10), (3, 4, 12), (2, 7, 14), (2, 3, 16), (4, 5, 22), (5, 6, 25)]
109
+
110
+ Variants of the previous example. ::
111
+
112
+ sage: H = Graph(G.edges(sort=True, labels=False))
113
+ sage: kruskal(H, check=True)
114
+ [(1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None), (3, 4, None), (4, 5, None)]
115
+ sage: G.allow_loops(True)
116
+ sage: G.allow_multiple_edges(True)
117
+ sage: G
118
+ Looped multi-graph on 7 vertices
119
+ sage: for i in range(20):
120
+ ....: u = randint(1, 7)
121
+ ....: v = randint(1, 7)
122
+ ....: w = randint(0, 20)
123
+ ....: G.add_edge(u, v, w)
124
+ sage: H = copy(G)
125
+ sage: H
126
+ Looped multi-graph on 7 vertices
127
+ sage: def sanitize(G):
128
+ ....: G.allow_loops(False)
129
+ ....: G.allow_multiple_edges(False, keep_label='min')
130
+ sage: sanitize(H)
131
+ sage: H
132
+ Graph on 7 vertices
133
+ sage: sum(e[2] for e in kruskal(G, check=True)) == sum(e[2] for e in kruskal(H, check=True))
134
+ True
135
+
136
+ An example from pages 599--601 in [GT2001]_. ::
137
+
138
+ sage: G = Graph({"SFO":{"BOS":2704, "ORD":1846, "DFW":1464, "LAX":337},
139
+ ....: "BOS":{"ORD":867, "JFK":187, "MIA":1258},
140
+ ....: "ORD":{"PVD":849, "JFK":740, "BWI":621, "DFW":802},
141
+ ....: "DFW":{"JFK":1391, "MIA":1121, "LAX":1235},
142
+ ....: "LAX":{"MIA":2342},
143
+ ....: "PVD":{"JFK":144},
144
+ ....: "JFK":{"MIA":1090, "BWI":184},
145
+ ....: "BWI":{"MIA":946}})
146
+ sage: G.weighted(True)
147
+ sage: kruskal(G, check=True)
148
+ [('JFK', 'PVD', 144),
149
+ ('BWI', 'JFK', 184),
150
+ ('BOS', 'JFK', 187),
151
+ ('LAX', 'SFO', 337),
152
+ ('BWI', 'ORD', 621),
153
+ ('DFW', 'ORD', 802),
154
+ ('BWI', 'MIA', 946),
155
+ ('DFW', 'LAX', 1235)]
156
+
157
+ An example from pages 568--569 in [CLRS2001]_. ::
158
+
159
+ sage: G = Graph({"a":{"b":4, "h":8}, "b":{"c":8, "h":11},
160
+ ....: "c":{"d":7, "f":4, "i":2}, "d":{"e":9, "f":14},
161
+ ....: "e":{"f":10}, "f":{"g":2}, "g":{"h":1, "i":6}, "h":{"i":7}})
162
+ sage: G.weighted(True)
163
+ sage: T = Graph(kruskal(G, check=True), format='list_of_edges')
164
+ sage: sum(T.edge_labels())
165
+ 37
166
+ sage: T.is_tree()
167
+ True
168
+
169
+ An example with custom edge labels::
170
+
171
+ sage: G = Graph([[0,1,1],[1,2,1],[2,0,10]], weighted=True)
172
+ sage: weight = lambda e:3-e[0]-e[1]
173
+ sage: sorted(kruskal(G, check=True))
174
+ [(0, 1, 1), (1, 2, 1)]
175
+ sage: sorted(kruskal(G, weight_function=weight, check=True))
176
+ [(0, 2, 10), (1, 2, 1)]
177
+ sage: sorted(kruskal(G, weight_function=weight, check=False))
178
+ [(0, 2, 10), (1, 2, 1)]
179
+
180
+ TESTS:
181
+
182
+ The input graph must not be empty. ::
183
+
184
+ sage: from sage.graphs.spanning_tree import kruskal
185
+ sage: kruskal(graphs.EmptyGraph(), check=True)
186
+ []
187
+ sage: kruskal(Graph(), check=True)
188
+ []
189
+ sage: kruskal(Graph(multiedges=True), check=True)
190
+ []
191
+ sage: kruskal(Graph(loops=True), check=True)
192
+ []
193
+ sage: kruskal(Graph(multiedges=True, loops=True), check=True)
194
+ []
195
+
196
+ The input graph must be connected. ::
197
+
198
+ sage: # long time
199
+ sage: def my_disconnected_graph(n, ntries, directed=False, multiedges=False, loops=False):
200
+ ....: G = Graph()
201
+ ....: k = randint(2, n)
202
+ ....: G.add_vertices(range(k))
203
+ ....: if directed:
204
+ ....: G = G.to_directed()
205
+ ....: if multiedges:
206
+ ....: G.allow_multiple_edges(True)
207
+ ....: if loops:
208
+ ....: G.allow_loops(True)
209
+ ....: for i in range(ntries):
210
+ ....: u = randint(0, k-1)
211
+ ....: v = randint(0, k-1)
212
+ ....: if u != v or loops:
213
+ ....: G.add_edge(u, v)
214
+ ....: while G.is_connected():
215
+ ....: u = randint(0, k-1)
216
+ ....: v = randint(0, k-1)
217
+ ....: G.delete_edge(u, v)
218
+ ....: return G
219
+ sage: G = my_disconnected_graph(100, 50, directed=False, multiedges=False, loops=False)
220
+ sage: kruskal(G, check=True)
221
+ []
222
+ sage: G = my_disconnected_graph(100, 50, directed=False, multiedges=True, loops=False)
223
+ sage: kruskal(G, check=True)
224
+ []
225
+ sage: G = my_disconnected_graph(100, 50, directed=False, multiedges=True, loops=True)
226
+ sage: kruskal(G, check=True)
227
+ []
228
+
229
+ If the input graph is a tree, then return its edges::
230
+
231
+ sage: T = graphs.RandomTree(randint(1, 50)) # long time
232
+ sage: sorted(T.edge_iterator()) == sorted(kruskal(T, check=True)) # long time
233
+ True
234
+
235
+ If the input is not a Graph::
236
+
237
+ sage: kruskal("I am not a graph")
238
+ Traceback (most recent call last):
239
+ ...
240
+ ValueError: the input graph must be undirected
241
+ sage: kruskal(digraphs.Path(10))
242
+ Traceback (most recent call last):
243
+ ...
244
+ ValueError: the input graph must be undirected
245
+
246
+ Check that the method is robust to incomparable vertices::
247
+
248
+ sage: G = Graph([(1, 2, 10), (1, 'a', 1), ('a', 'b', 1), ('b', 2, 1)])
249
+ sage: E = kruskal(G, by_weight=True)
250
+ sage: sum(w for _, _, w in E)
251
+ 3
252
+ """
253
+ return list(kruskal_iterator(G, by_weight=by_weight, weight_function=weight_function,
254
+ check_weight=check_weight, check=check))
255
+
256
+
257
+ def kruskal_iterator(G, by_weight=True, weight_function=None, check_weight=False, bint check=False):
258
+ """
259
+ Return an iterator implementation of Kruskal algorithm.
260
+
261
+ INPUT:
262
+
263
+ - ``G`` -- an undirected graph
264
+
265
+ - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in
266
+ the graph are weighted. If ``False``, all edges have weight 1.
267
+
268
+ - ``weight_function`` -- function (default: ``None``); a function that takes
269
+ as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``,
270
+ ``by_weight`` is automatically set to ``True``. If ``None`` and
271
+ ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not
272
+ ``None``, else ``1`` as a weight.
273
+
274
+ - ``check_weight`` -- boolean (default: ``False``); whether to check that
275
+ the ``weight_function`` outputs a number for each edge
276
+
277
+ - ``check`` -- boolean (default: ``False``); whether to first perform sanity
278
+ checks on the input graph ``G``. Default: ``check=False``. If we toggle
279
+ ``check=True``, the following sanity checks are first performed on ``G``
280
+ prior to running Kruskal's algorithm on that input graph:
281
+
282
+ - Is ``G`` the null graph?
283
+ - Is ``G`` disconnected?
284
+ - Is ``G`` a tree?
285
+ - Does ``G`` have self-loops?
286
+ - Does ``G`` have multiple edges?
287
+
288
+ By default, we turn off the sanity checks for performance reasons. This
289
+ means that by default the function assumes that its input graph is
290
+ connected, and has at least one vertex. Otherwise, you should set
291
+ ``check=True`` to perform some sanity checks and preprocessing on the
292
+ input graph. If ``G`` has multiple edges or self-loops, the algorithm
293
+ still works, but the running-time can be improved if these edges are
294
+ removed. To further improve the runtime of this function, you should call
295
+ it directly instead of using it indirectly via
296
+ :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`.
297
+
298
+ OUTPUT: the edges of a minimum spanning tree of ``G``, one by one
299
+
300
+ .. SEEALSO:: :func:`kruskal`
301
+
302
+ EXAMPLES::
303
+
304
+ sage: from sage.graphs.spanning_tree import kruskal_iterator
305
+ 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}})
306
+ sage: G.weighted(True)
307
+ sage: next(kruskal_iterator(G, check=True))
308
+ (1, 6, 10)
309
+
310
+ TESTS:
311
+
312
+ If the input is not a Graph::
313
+
314
+ sage: list(kruskal_iterator("I am not a graph"))
315
+ Traceback (most recent call last):
316
+ ...
317
+ ValueError: the input graph must be undirected
318
+ sage: list(kruskal_iterator(digraphs.Path(2)))
319
+ Traceback (most recent call last):
320
+ ...
321
+ ValueError: the input graph must be undirected
322
+
323
+ Check that the method is robust to incomparable vertices::
324
+
325
+ sage: G = Graph([(1, 2, 10), (1, 'a', 1), ('a', 'b', 1), ('b', 2, 1)])
326
+ sage: E = list(kruskal_iterator(G, by_weight=True))
327
+ sage: sum(w for _, _, w in E)
328
+ 3
329
+ """
330
+ from sage.graphs.graph import Graph
331
+ if not isinstance(G, Graph):
332
+ raise ValueError("the input graph must be undirected")
333
+
334
+ # sanity checks
335
+ if check:
336
+ if not G.order():
337
+ return
338
+ if not G.is_connected():
339
+ return
340
+ # G is now assumed to be a nonempty connected graph
341
+ if G.num_verts() == G.num_edges() + 1:
342
+ # G is a tree
343
+ yield from G.edge_iterator()
344
+ return
345
+
346
+ cdef DisjointSet_of_hashables union_find = DisjointSet_of_hashables(G)
347
+ by_weight, weight_function = G._get_weight_function(by_weight=by_weight,
348
+ weight_function=weight_function,
349
+ check_weight=check_weight)
350
+ yield from kruskal_iterator_from_edges(G.edge_iterator(), union_find,
351
+ by_weight=by_weight,
352
+ weight_function=weight_function,
353
+ check_weight=False)
354
+
355
+
356
+ def kruskal_iterator_from_edges(edges, union_find, by_weight=True,
357
+ weight_function=None, check_weight=False):
358
+ """
359
+ Return an iterator implementation of Kruskal algorithm on list of edges.
360
+
361
+ INPUT:
362
+
363
+ - ``edges`` -- list of edges
364
+
365
+ - ``union_find`` -- a
366
+ :class:`~sage.sets.disjoint_set.DisjointSet_of_hashables` encoding a
367
+ forest
368
+
369
+ - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in
370
+ the graph are weighted. If ``False``, all edges have weight 1.
371
+
372
+ - ``weight_function`` -- function (default: ``None``); a function that takes
373
+ as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``,
374
+ ``by_weight`` is automatically set to ``True``. If ``None`` and
375
+ ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not
376
+ ``None``, else ``1`` as a weight.
377
+
378
+ - ``check_weight`` -- boolean (default: ``False``); whether to check that
379
+ the ``weight_function`` outputs a number for each edge
380
+
381
+ OUTPUT: the edges of a minimum spanning tree of ``G``, one by one
382
+
383
+ .. SEEALSO::
384
+
385
+ - :func:`kruskal`
386
+ - :func:`filter_kruskal`
387
+
388
+ EXAMPLES::
389
+
390
+ sage: from sage.graphs.spanning_tree import kruskal_iterator_from_edges
391
+ 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}})
392
+ sage: G.weighted(True)
393
+ sage: union_set = DisjointSet(G)
394
+ sage: next(kruskal_iterator_from_edges(G.edges(sort=False), union_set, by_weight=G.weighted()))
395
+ (1, 6, 10)
396
+
397
+ Check that the method is robust to incomparable vertices::
398
+
399
+ sage: G = Graph([(1, 2, 10), (1, 'a', 1), ('a', 'b', 1), ('b', 2, 1)])
400
+ sage: union_set = DisjointSet(G)
401
+ sage: E = list(kruskal_iterator_from_edges(G.edges(sort=False), union_set, by_weight=True))
402
+ sage: sum(w for _, _, w in E)
403
+ 3
404
+ """
405
+ # We sort edges, as specified.
406
+ if weight_function is not None:
407
+ edges = sorted(edges, key=weight_function)
408
+ elif by_weight:
409
+ from operator import itemgetter
410
+ edges = sorted(edges, key=itemgetter(2))
411
+
412
+ # Kruskal's algorithm
413
+ for e in edges:
414
+ # acyclic test via union-find
415
+ u = union_find.find(e[0])
416
+ v = union_find.find(e[1])
417
+ if u != v:
418
+ yield e
419
+ # merge the trees
420
+ union_find.union(u, v)
421
+ if union_find.number_of_subsets() == 1:
422
+ return
423
+
424
+
425
+ def filter_kruskal(G, threshold=10000, by_weight=True, weight_function=None,
426
+ check_weight=True, bint check=False):
427
+ """
428
+ Minimum spanning tree using Filter Kruskal algorithm.
429
+
430
+ This function implements the variant of Kruskal's algorithm proposed in
431
+ [OSS2009]_. Instead of directly sorting the whole set of edges, it
432
+ partitions it in a similar way to quicksort and filter out edges that
433
+ connect vertices of the same tree to reduce the cost of sorting.
434
+
435
+ This function assumes that we can only compute minimum spanning trees for
436
+ undirected graphs. Such graphs can be weighted or unweighted, and they can
437
+ have multiple edges (since we are computing the minimum spanning tree, only
438
+ the minimum weight among all `(u,v)`-edges is considered, for each pair of
439
+ vertices `u`, `v`).
440
+
441
+ INPUT:
442
+
443
+ - ``G`` -- an undirected graph
444
+
445
+ - ``threshold`` -- integer (default: 10000); maximum number of edges on
446
+ which to run kruskal algorithm. Above that value, edges are partitioned
447
+ into sets of size at most ``threshold``
448
+
449
+ - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in
450
+ the graph are weighted. If ``False``, all edges have weight 1.
451
+
452
+ - ``weight_function`` -- function (default: ``None``); a function that takes
453
+ as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``,
454
+ ``by_weight`` is automatically set to ``True``. If ``None`` and
455
+ ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not
456
+ ``None``, else ``1`` as a weight.
457
+
458
+ - ``check_weight`` -- boolean (default: ``False``); whether to check that
459
+ the ``weight_function`` outputs a number for each edge
460
+
461
+ - ``check`` -- boolean (default: ``False``); whether to first perform sanity
462
+ checks on the input graph ``G``. Default: ``check=False``. If we toggle
463
+ ``check=True``, the following sanity checks are first performed on ``G``
464
+ prior to running Kruskal's algorithm on that input graph:
465
+
466
+ - Is ``G`` the null graph?
467
+ - Is ``G`` disconnected?
468
+ - Is ``G`` a tree?
469
+ - Does ``G`` have self-loops?
470
+ - Does ``G`` have multiple edges?
471
+
472
+ OUTPUT:
473
+
474
+ The edges of a minimum spanning tree of ``G``, if one exists, otherwise
475
+ returns the empty list.
476
+
477
+ .. SEEALSO::
478
+
479
+ - :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`
480
+ - :wikipedia:`Kruskal%27s_algorithm`
481
+ - :func:`kruskal`
482
+ - :func:`filter_kruskal_iterator`
483
+
484
+ EXAMPLES::
485
+
486
+ sage: from sage.graphs.spanning_tree import filter_kruskal
487
+ 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}})
488
+ sage: G.weighted(True)
489
+ sage: filter_kruskal(G, check=True)
490
+ [(1, 6, 10), (3, 4, 12), (2, 7, 14), (2, 3, 16), (4, 5, 22), (5, 6, 25)]
491
+
492
+ sage: filter_kruskal(Graph(2), check=True)
493
+ []
494
+
495
+ TESTS:
496
+
497
+ Check that the method is robust to incomparable vertices::
498
+
499
+ sage: G = Graph([(1, 2, 10), (1, 'a', 1), ('a', 'b', 1), ('b', 2, 1)])
500
+ sage: E = filter_kruskal(G, by_weight=True)
501
+ sage: sum(w for _, _, w in E)
502
+ 3
503
+ """
504
+ return list(filter_kruskal_iterator(G, threshold=threshold,
505
+ by_weight=by_weight, weight_function=weight_function,
506
+ check_weight=check_weight, check=check))
507
+
508
+
509
+ def filter_kruskal_iterator(G, threshold=10000, by_weight=True, weight_function=None,
510
+ check_weight=True, bint check=False):
511
+ r"""
512
+ Return an iterator implementation of Filter Kruskal's algorithm.
513
+
514
+ INPUT:
515
+
516
+ - ``G`` -- an undirected graph
517
+
518
+ - ``threshold`` -- integer (default: 10000); maximum number of edges on
519
+ which to run kruskal algorithm. Above that value, edges are partitioned
520
+ into sets of size at most ``threshold``
521
+
522
+ - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in
523
+ the graph are weighted. If ``False``, all edges have weight 1.
524
+
525
+ - ``weight_function`` -- function (default: ``None``); a function that takes
526
+ as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``,
527
+ ``by_weight`` is automatically set to ``True``. If ``None`` and
528
+ ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not
529
+ ``None``, else ``1`` as a weight.
530
+
531
+ - ``check_weight`` -- boolean (default: ``False``); whether to check that
532
+ the ``weight_function`` outputs a number for each edge
533
+
534
+ - ``check`` -- boolean (default: ``False``); whether to first perform sanity
535
+ checks on the input graph ``G``. Default: ``check=False``. If we toggle
536
+ ``check=True``, the following sanity checks are first performed on ``G``
537
+ prior to running Kruskal's algorithm on that input graph:
538
+
539
+ - Is ``G`` the null graph?
540
+ - Is ``G`` disconnected?
541
+ - Is ``G`` a tree?
542
+ - Does ``G`` have self-loops?
543
+ - Does ``G`` have multiple edges?
544
+
545
+ OUTPUT: the edges of a minimum spanning tree of ``G``, one by one
546
+
547
+ .. SEEALSO::
548
+
549
+ - :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`
550
+ - :wikipedia:`Kruskal%27s_algorithm`
551
+ - :func:`kruskal`
552
+ - :func:`filter_kruskal`
553
+
554
+ EXAMPLES:
555
+
556
+ The edges of a minimum spanning tree of ``G``, if one exists, otherwise
557
+ returns the empty list. ::
558
+
559
+ sage: from sage.graphs.spanning_tree import filter_kruskal_iterator
560
+ 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}})
561
+ sage: G.weighted(True)
562
+ sage: list(filter_kruskal_iterator(G, threshold=3, check=True))
563
+ [(1, 6, 10), (3, 4, 12), (2, 7, 14), (2, 3, 16), (4, 5, 22), (5, 6, 25)]
564
+
565
+ The weights of the spanning trees returned by :func:`kruskal_iterator` and
566
+ :func:`filter_kruskal_iterator` are the same::
567
+
568
+ sage: # needs networkx
569
+ sage: from sage.graphs.spanning_tree import kruskal_iterator
570
+ sage: G = graphs.RandomBarabasiAlbert(50, 2)
571
+ sage: for u, v in G.edge_iterator(labels=False):
572
+ ....: G.set_edge_label(u, v, randint(1, 10))
573
+ sage: G.weighted(True)
574
+ sage: sum(e[2] for e in kruskal_iterator(G)) == sum(e[2]
575
+ ....: for e in filter_kruskal_iterator(G, threshold=20))
576
+ True
577
+
578
+ TESTS:
579
+
580
+ The threshold must be at least 1::
581
+
582
+ sage: from sage.graphs.spanning_tree import filter_kruskal_iterator
583
+ sage: next(filter_kruskal_iterator(Graph(), threshold=0))
584
+ Traceback (most recent call last):
585
+ ...
586
+ ValueError: the threshold mut be at least 1
587
+
588
+ Check that a threshold of 1 is accepted::
589
+
590
+ sage: len(list(filter_kruskal_iterator(graphs.HouseGraph(), threshold=1)))
591
+ 4
592
+
593
+ Check that the method is robust to incomparable vertices::
594
+
595
+ sage: G = Graph([(1, 2, 10), (1, 'a', 1), ('a', 'b', 1), ('b', 2, 1)])
596
+ sage: E = list(filter_kruskal_iterator(G, by_weight=True))
597
+ sage: sum(w for _, _, w in E)
598
+ 3
599
+ """
600
+ from sage.graphs.graph import Graph
601
+ if not isinstance(G, Graph):
602
+ raise ValueError("the input graph must be undirected")
603
+ if threshold < 1:
604
+ raise ValueError("the threshold mut be at least 1")
605
+ if check:
606
+ if not G.order() or not G.is_connected():
607
+ return
608
+ # G is now assumed to be a nonempty connected graph
609
+ if G.order() == G.size() + 1:
610
+ # G is a tree
611
+ yield from G.edge_iterator()
612
+ return
613
+
614
+ g = G.to_simple(to_undirected=False, keep_label='min')
615
+ else:
616
+ g = G
617
+
618
+ cdef int m = g.size()
619
+ if m <= threshold:
620
+ yield from kruskal_iterator_from_edges(g.edge_iterator(),
621
+ DisjointSet_of_hashables(g),
622
+ by_weight=by_weight,
623
+ weight_function=weight_function,
624
+ check_weight=check_weight)
625
+ return
626
+
627
+ #
628
+ # Initialize some data structure
629
+ #
630
+ cdef list edges = list(g.edge_iterator())
631
+ # Precompute edge weights to avoid frequent calls to weight_function
632
+ cdef list weight
633
+ _, weight_function = G._get_weight_function(by_weight=by_weight,
634
+ weight_function=weight_function,
635
+ check_weight=check_weight)
636
+ if weight_function is None:
637
+ weight = [1 for _ in range(m)]
638
+ else:
639
+ weight = [weight_function(e) for e in edges]
640
+
641
+ cdef MemoryAllocator mem = MemoryAllocator()
642
+ # Array storing a permutation of the edges.
643
+ # e_index[i] is the position of edge i in list edges
644
+ cdef int* e_index = <int*> mem.allocarray(m, sizeof(int))
645
+ cdef int i, j
646
+ for i in range(m):
647
+ e_index[i] = i
648
+ # Stack of range of edge partitions
649
+ cdef list stack = [(0, m - 1)]
650
+ cdef int begin, end
651
+ # Parameter to equally divide edges with weight equal the to pivot
652
+ cdef bint ch = True
653
+ # Data structure to record the vertices in each tree of the forest
654
+ cdef DisjointSet_of_hashables union_find = DisjointSet_of_hashables(g)
655
+
656
+ #
657
+ # Iteratively partition the list of edges
658
+ #
659
+ while stack:
660
+ begin, end = stack.pop()
661
+
662
+ if end - begin < threshold:
663
+ # Filter edges connecting vertices of a same tree
664
+ L = [edges[e_index[i]] for i in range(begin, end + 1)
665
+ if union_find.find(edges[e_index[i]][0]) != union_find.find(edges[e_index[i]][1])]
666
+ yield from kruskal_iterator_from_edges(L, union_find,
667
+ by_weight=by_weight,
668
+ weight_function=weight_function,
669
+ check_weight=False)
670
+ if union_find.number_of_subsets() == 1:
671
+ return
672
+ continue
673
+
674
+ # Choose a pivot
675
+ pivot = weight[e_index[(begin + end) // 2]]
676
+
677
+ # Partition edges with respect to pivot, as in quicksort
678
+ i, j = begin, end
679
+ while i < j:
680
+ while weight[e_index[i]] < pivot and i < j:
681
+ i += 1
682
+ if ch and weight[e_index[i]] == pivot and i < j:
683
+ i += 1
684
+ ch = False
685
+ continue
686
+ while weight[e_index[j]] > pivot and i < j:
687
+ j -= 1
688
+ if not ch and weight[e_index[j]] == pivot and i < j:
689
+ j -= 1
690
+ ch = True
691
+ continue
692
+ if i < j:
693
+ e_index[i], e_index[j] = e_index[j], e_index[i]
694
+
695
+ # Record range of edge partitions
696
+ if weight[e_index[i]] <= pivot:
697
+ stack.append((i + 1, end))
698
+ stack.append((begin, i))
699
+ else:
700
+ stack.append((i, end))
701
+ stack.append((begin, i - 1))
702
+
703
+
704
+ def boruvka(G, by_weight=True, weight_function=None, check_weight=True, check=False):
705
+ r"""
706
+ Minimum spanning tree using Boruvka's algorithm.
707
+
708
+ This function assumes that we can only compute minimum spanning trees for
709
+ undirected graphs. Such graphs can be weighted or unweighted, and they can
710
+ have multiple edges (since we are computing the minimum spanning tree, only
711
+ the minimum weight among all `(u,v)`-edges is considered, for each pair of
712
+ vertices `u`, `v`).
713
+
714
+ INPUT:
715
+
716
+ - ``G`` -- an undirected graph
717
+
718
+ - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in
719
+ the graph are weighted. If ``False``, all edges have weight 1
720
+
721
+ - ``weight_function`` -- function (default: ``None``); a function that takes
722
+ as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``,
723
+ ``by_weight`` is automatically set to ``True``. If ``None`` and
724
+ ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not
725
+ ``None``, else ``1`` as a weight.
726
+
727
+ - ``check_weight`` -- boolean (default: ``False``); whether to check that
728
+ the ``weight_function`` outputs a number for each edge
729
+
730
+ - ``check`` -- boolean (default: ``False``); whether to first perform sanity
731
+ checks on the input graph ``G``. Default: ``check=False``. If we toggle
732
+ ``check=True``, the following sanity checks are first performed on ``G``
733
+ prior to running Boruvka's algorithm on that input graph:
734
+
735
+ - Is ``G`` the null graph or graph on one vertex?
736
+ - Is ``G`` disconnected?
737
+ - Is ``G`` a tree?
738
+
739
+ By default, we turn off the sanity checks for performance reasons. This
740
+ means that by default the function assumes that its input graph is
741
+ connected, and has at least one vertex. Otherwise, you should set
742
+ ``check=True`` to perform some sanity checks and preprocessing on the
743
+ input graph.
744
+
745
+ OUTPUT:
746
+
747
+ The edges of a minimum spanning tree of ``G``, if one exists, otherwise
748
+ returns the empty list.
749
+
750
+ .. SEEALSO::
751
+
752
+ - :meth:`~sage.graphs.generic_graph.GenericGraph.min_spanning_tree`
753
+
754
+ EXAMPLES:
755
+
756
+ An example from pages 727--728 in [Sah2000]_::
757
+
758
+ sage: from sage.graphs.spanning_tree import boruvka
759
+ 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}})
760
+ sage: G.weighted(True)
761
+ sage: E = boruvka(G, check=True); E
762
+ [(1, 6, 10), (2, 7, 14), (3, 4, 12), (4, 5, 22), (5, 6, 25), (2, 3, 16)]
763
+ sage: boruvka(G, by_weight=True)
764
+ [(1, 6, 10), (2, 7, 14), (3, 4, 12), (4, 5, 22), (5, 6, 25), (2, 3, 16)]
765
+ sage: sorted(boruvka(G, by_weight=False))
766
+ [(1, 2, 28), (1, 6, 10), (2, 3, 16), (2, 7, 14), (3, 4, 12), (4, 5, 22)]
767
+
768
+ An example with custom edge labels::
769
+
770
+ sage: G = Graph([[0,1,1],[1,2,1],[2,0,10]], weighted=True)
771
+ sage: weight = lambda e:3-e[0]-e[1]
772
+ sage: boruvka(G, weight_function=lambda e:3-e[0]-e[1], by_weight=True)
773
+ [(0, 2, 10), (1, 2, 1)]
774
+ sage: boruvka(G, weight_function=lambda e:float(1/e[2]), by_weight=True)
775
+ [(0, 2, 10), (0, 1, 1)]
776
+
777
+ An example of disconnected graph with ``check`` disabled::
778
+
779
+ sage: from sage.graphs.spanning_tree import boruvka
780
+ sage: G = Graph({1:{2:28}, 3:{4:16}}, weighted=True)
781
+ sage: boruvka(G, check=False)
782
+ []
783
+
784
+ TESTS:
785
+
786
+ If the input graph is a tree, then return its edges::
787
+
788
+ sage: T = graphs.RandomTree(randint(1, 10))
789
+ sage: list(T.edges(sort=True)) == sorted(boruvka(T, check=True))
790
+ True
791
+
792
+ Check if the weight of MST returned by Prim's and Boruvka's is the same::
793
+
794
+ sage: G = Graph([(u,v,randint(1,5)) for u,v in graphs.CompleteGraph(4).edges(sort=True, labels=0)], weighted=True)
795
+ sage: G.weighted()
796
+ True
797
+ sage: E1 = G.min_spanning_tree(algorithm='Boruvka')
798
+ sage: E2 = G.min_spanning_tree(algorithm='Prim_Boost')
799
+ sage: sum(e[2] for e in E1) == sum(e[2] for e in E2)
800
+ True
801
+
802
+ If the input is not a Graph::
803
+
804
+ sage: boruvka("I am not a graph")
805
+ Traceback (most recent call last):
806
+ ...
807
+ ValueError: the input graph must be undirected
808
+ sage: boruvka(digraphs.Path(10))
809
+ Traceback (most recent call last):
810
+ ...
811
+ ValueError: the input graph must be undirected
812
+
813
+ Check that the method is robust to incomparable vertices::
814
+
815
+ sage: G = Graph([(1, 2, 10), (1, 'a', 1), ('a', 'b', 1), ('b', 2, 1)])
816
+ sage: E = boruvka(G, by_weight=True)
817
+ sage: sum(w for _, _, w in E)
818
+ 3
819
+ """
820
+ from sage.graphs.graph import Graph
821
+ if not isinstance(G, Graph):
822
+ raise ValueError("the input graph must be undirected")
823
+
824
+ if G.order() <= 1:
825
+ return []
826
+
827
+ # sanity checks
828
+ if check:
829
+ if not G.is_connected():
830
+ return []
831
+ # G is now assumed to be a nonempty connected graph
832
+ if G.num_verts() == G.num_edges() + 1:
833
+ # G is a tree
834
+ return G.edges(sort=False)
835
+
836
+ by_weight, weight_function = G._get_weight_function(by_weight=by_weight,
837
+ weight_function=weight_function,
838
+ check_weight=check_weight)
839
+
840
+ # Boruvka's algorithm
841
+
842
+ # Store the list of active edges as (e, e_weight) in a list
843
+ if weight_function is not None:
844
+ edge_list = [(e, weight_function(e)) for e in G.edge_iterator()]
845
+ else:
846
+ edge_list = [(e, 1) for e in G.edge_iterator()]
847
+
848
+ # initially, each vertex is a connected component
849
+ cdef DisjointSet_of_hashables partitions = DisjointSet_of_hashables(G)
850
+ # a dictionary to store the least weight outgoing edge for each component
851
+ cdef dict cheapest = {}
852
+ cdef list T = [] # stores the edges in minimum spanning tree
853
+ cdef int numConComp = G.order()
854
+ cdef int numConCompPrevIter = numConComp + 1
855
+
856
+ # Dictionary to maintain active cheapest edges between pairs of components
857
+ cdef dict components_dict = {}
858
+
859
+ while numConComp > 1:
860
+ # Check if number of connected components decreased.
861
+ # Otherwise, the graph is not connected.
862
+ if numConCompPrevIter == numConComp:
863
+ return []
864
+ else:
865
+ numConCompPrevIter = numConComp
866
+
867
+ # Iterate over all active edges to identify the cheapest edge between
868
+ # each pair of components (trees of the forest), as well as cheapest
869
+ # active edge incident to a component.
870
+ for e, e_weight in edge_list:
871
+ component1 = partitions.find(e[0])
872
+ component2 = partitions.find(e[1])
873
+
874
+ if component1 != component2:
875
+ if component1 in cheapest:
876
+ if cheapest[component1][1] > e_weight:
877
+ cheapest[component1] = (e, e_weight)
878
+ else:
879
+ cheapest[component1] = (e, e_weight)
880
+
881
+ if component2 in cheapest:
882
+ if cheapest[component2][1] > e_weight:
883
+ cheapest[component2] = (e, e_weight)
884
+ else:
885
+ cheapest[component2] = (e, e_weight)
886
+ # store the cheapest edge between the two components
887
+ pair = frozenset((component1, component2))
888
+ if pair in components_dict:
889
+ if components_dict[pair][1] > e_weight:
890
+ components_dict[pair] = (e, e_weight)
891
+ else:
892
+ components_dict[pair] = (e, e_weight)
893
+
894
+ # Update the list of active edges
895
+ edge_list = components_dict.values()
896
+
897
+ # Go through all the current connected components and merge wherever
898
+ # possible
899
+ for v in cheapest:
900
+ e, e_weight = cheapest[v]
901
+ component1 = partitions.find(e[0])
902
+ component2 = partitions.find(e[1])
903
+
904
+ if component1 != component2:
905
+ partitions.union(component1, component2)
906
+ T.append(e)
907
+ numConComp = numConComp - 1
908
+
909
+ # reset the dictionaries for next iteration
910
+ cheapest = {}
911
+ components_dict = {}
912
+
913
+ return T
914
+
915
+
916
+ def random_spanning_tree(G, output_as_graph=False, by_weight=False, weight_function=None, check_weight=True):
917
+ r"""
918
+ Return a random spanning tree of the graph.
919
+
920
+ This uses the Aldous-Broder algorithm ([Bro1989]_, [Ald1990]_) to generate
921
+ a random spanning tree with the uniform distribution, as follows.
922
+
923
+ Start from any vertex. Perform a random walk by choosing at every step one
924
+ neighbor uniformly at random. Every time a new vertex `j` is met, add the
925
+ edge `(i, j)` to the spanning tree, where `i` is the previous vertex in the
926
+ random walk.
927
+
928
+ When ``by_weight`` is ``True`` or a weight function is given, the selection
929
+ of the neighbor is done proportionaly to the edge weights.
930
+
931
+ INPUT:
932
+
933
+ - ``G`` -- an undirected graph
934
+
935
+ - ``output_as_graph`` -- boolean (default: ``False``); whether to return a
936
+ list of edges or a graph
937
+
938
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges in
939
+ the graph are weighted, otherwise all edges have weight 1
940
+
941
+ - ``weight_function`` -- function (default: ``None``); a function that takes
942
+ as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``,
943
+ ``by_weight`` is automatically set to ``True``. If ``None`` and
944
+ ``by_weight`` is ``True``, we use the edge label ``l`` , if ``l`` is not
945
+ ``None``, else ``1`` as a weight. The ``weight_function`` can be used to
946
+ transform the label into a weight (note that, if the weight returned is
947
+ not convertible to a float, an error is raised)
948
+
949
+ - ``check_weight`` -- boolean (default: ``True``); whether to check that
950
+ the ``weight_function`` outputs a number for each edge
951
+
952
+ .. SEEALSO::
953
+
954
+ :meth:`~sage.graphs.generic_graph.GenericGraph.spanning_trees_count`
955
+ and :meth:`~sage.graphs.graph.Graph.spanning_trees`
956
+
957
+ EXAMPLES::
958
+
959
+ sage: G = graphs.TietzeGraph()
960
+ sage: G.random_spanning_tree(output_as_graph=True)
961
+ Graph on 12 vertices
962
+ sage: rg = G.random_spanning_tree(); rg # random
963
+ [(0, 9),
964
+ (9, 11),
965
+ (0, 8),
966
+ (8, 7),
967
+ (7, 6),
968
+ (7, 2),
969
+ (2, 1),
970
+ (1, 5),
971
+ (9, 10),
972
+ (5, 4),
973
+ (2, 3)]
974
+ sage: Graph(rg).is_tree()
975
+ True
976
+
977
+ A visual example for the grid graph::
978
+
979
+ sage: G = graphs.Grid2dGraph(6, 6)
980
+ sage: pos = G.get_pos()
981
+ sage: T = G.random_spanning_tree(True)
982
+ sage: T.set_pos(pos)
983
+ sage: T.show(vertex_labels=False) # needs sage.plot
984
+
985
+ We can also use edge weights to change the probability of returning a
986
+ spanning tree::
987
+
988
+ sage: def foo(G, k):
989
+ ....: S = set()
990
+ ....: for _ in range(k):
991
+ ....: E = G.random_spanning_tree(by_weight=True)
992
+ ....: S.add(Graph(E).graph6_string())
993
+ ....: return S
994
+ sage: K3 = graphs.CompleteGraph(3)
995
+ sage: for u, v in K3.edges(sort=True, labels=False):
996
+ ....: K3.set_edge_label(u, v, randint(1, 2))
997
+ sage: foo(K3, 100) == {'BW', 'Bg', 'Bo'} # random
998
+ True
999
+ sage: K4 = graphs.CompleteGraph(4)
1000
+ sage: for u, v in K4.edges(sort=True, labels=False):
1001
+ ....: K4.set_edge_label(u, v, randint(1, 2))
1002
+ sage: print(len(foo(K4, 100))) # random
1003
+ 16
1004
+
1005
+ Check that the spanning tree returned when using weights is a tree::
1006
+
1007
+ sage: # needs networkx
1008
+ sage: G = graphs.RandomBarabasiAlbert(50, 2)
1009
+ sage: for u, v in G.edge_iterator(labels=False):
1010
+ ....: G.set_edge_label(u, v, randint(1, 10))
1011
+ sage: T = G.random_spanning_tree(by_weight=True, output_as_graph=True)
1012
+ sage: T.is_tree()
1013
+ True
1014
+
1015
+ TESTS::
1016
+
1017
+ sage: G = Graph()
1018
+ sage: G.random_spanning_tree()
1019
+ Traceback (most recent call last):
1020
+ ...
1021
+ ValueError: works only for non-empty connected graphs
1022
+
1023
+ sage: G = graphs.CompleteGraph(3).complement()
1024
+ sage: G.random_spanning_tree()
1025
+ Traceback (most recent call last):
1026
+ ...
1027
+ ValueError: works only for non-empty connected graphs
1028
+
1029
+ Check that the method is robust to incomparable vertices::
1030
+
1031
+ sage: G = Graph([(1, 2, 10), (1, 'a', 1), ('a', 'b', 1), ('b', 2, 1)])
1032
+ sage: T = G.random_spanning_tree(by_weight=True, output_as_graph=True)
1033
+ sage: T.is_tree()
1034
+ True
1035
+ """
1036
+ from sage.misc.prandom import randint
1037
+ from sage.misc.prandom import random
1038
+ from sage.graphs.graph import Graph
1039
+
1040
+ cdef int N = G.order()
1041
+
1042
+ if not N or not G.is_connected():
1043
+ raise ValueError('works only for non-empty connected graphs')
1044
+
1045
+ if G.order() == G.size() + 1:
1046
+ # G is a tree
1047
+ if output_as_graph:
1048
+ return G.copy()
1049
+ return list(G.edge_iterator(label=False))
1050
+
1051
+ by_weight, weight_function = G._get_weight_function(by_weight=by_weight,
1052
+ weight_function=weight_function,
1053
+ check_weight=check_weight)
1054
+
1055
+ if by_weight:
1056
+ def next_neighbor(s):
1057
+ p = random() * sum(weight_function(e)
1058
+ for e in G.edge_iterator(s, sort_vertices=False))
1059
+ for e in G.edge_iterator(s, sort_vertices=False):
1060
+ p -= weight_function(e)
1061
+ if p <= 0:
1062
+ break
1063
+ return e[1] if e[0] == s else e[0]
1064
+ else:
1065
+ def next_neighbor(s):
1066
+ return G.neighbors(s)[randint(0, G.degree(s) - 1)]
1067
+
1068
+ s = next(G.vertex_iterator())
1069
+ cdef set found = set([s])
1070
+ cdef int found_nr = 1
1071
+ cdef list tree_edges = []
1072
+ while found_nr < N:
1073
+ new_s = next_neighbor(s)
1074
+ if new_s not in found:
1075
+ found.add(new_s)
1076
+ found_nr += 1
1077
+ tree_edges.append((s, new_s))
1078
+ s = new_s
1079
+
1080
+ if not output_as_graph:
1081
+ return tree_edges
1082
+ return Graph(tree_edges)
1083
+
1084
+
1085
+ def spanning_trees(g, labels=False):
1086
+ r"""
1087
+ Return an iterator over all spanning trees of the graph `g`.
1088
+
1089
+ A disconnected graph has no spanning tree.
1090
+
1091
+ Uses the Read-Tarjan backtracking algorithm [RT1975a]_.
1092
+
1093
+ INPUT:
1094
+
1095
+ - ``labels`` -- boolean (default: ``False``); whether to return edges labels
1096
+ in the spanning trees or not
1097
+
1098
+ EXAMPLES::
1099
+
1100
+ sage: G = Graph([(1,2),(1,2),(1,3),(1,3),(2,3),(1,4)], multiedges=True)
1101
+ sage: len(list(G.spanning_trees()))
1102
+ 8
1103
+ sage: G.spanning_trees_count() # needs sage.modules
1104
+ 8
1105
+ sage: G = Graph([(1,2),(2,3),(3,1),(3,4),(4,5),(4,5),(4,6)], multiedges=True)
1106
+ sage: len(list(G.spanning_trees()))
1107
+ 6
1108
+ sage: G.spanning_trees_count() # needs sage.modules
1109
+ 6
1110
+
1111
+ .. SEEALSO::
1112
+
1113
+ - :meth:`~sage.graphs.generic_graph.GenericGraph.spanning_trees_count`
1114
+ -- counts the number of spanning trees
1115
+
1116
+ - :meth:`~sage.graphs.graph.Graph.random_spanning_tree`
1117
+ -- returns a random spanning tree
1118
+
1119
+ TESTS:
1120
+
1121
+ Works with looped graphs::
1122
+
1123
+ sage: g = Graph({i:[i,(i+1)%6] for i in range(6)})
1124
+ sage: list(g.spanning_trees())
1125
+ [Graph on 6 vertices,
1126
+ Graph on 6 vertices,
1127
+ Graph on 6 vertices,
1128
+ Graph on 6 vertices,
1129
+ Graph on 6 vertices,
1130
+ Graph on 6 vertices]
1131
+
1132
+ Edges of the spanning trees can be labeled or unlabeled (:issue:`27557`)::
1133
+
1134
+ sage: g = Graph([(1,2,2),(1,2,1),(1,2,4),(1,4,5)],multiedges=True)
1135
+ sage: l = list(g.spanning_trees(labels=True))
1136
+ sage: l[0].edges(sort=True)
1137
+ [(1, 2, 4), (1, 4, 5)]
1138
+ sage: l[1].edges(sort=True)
1139
+ [(1, 2, 1), (1, 4, 5)]
1140
+ sage: l[2].edges(sort=True)
1141
+ [(1, 2, 2), (1, 4, 5)]
1142
+
1143
+ Small cases::
1144
+
1145
+ sage: list(Graph().spanning_trees())
1146
+ []
1147
+ sage: list(Graph(1).spanning_trees())
1148
+ [Graph on 1 vertex]
1149
+ sage: list(Graph(2).spanning_trees())
1150
+ []
1151
+
1152
+ Giving anything else than a graph::
1153
+
1154
+ sage: from sage.graphs.spanning_tree import spanning_trees
1155
+ sage: list(spanning_trees(DiGraph()))
1156
+ Traceback (most recent call last):
1157
+ ...
1158
+ ValueError: this method is for undirected graphs only
1159
+ sage: list(spanning_trees("bike"))
1160
+ Traceback (most recent call last):
1161
+ ...
1162
+ ValueError: this method is for undirected graphs only
1163
+
1164
+ Check that the method is robust to incomparable vertices::
1165
+
1166
+ sage: G = Graph([(1, 2, 10), (1, 'a', 1), ('a', 'b', 1), ('b', 2, 1)])
1167
+ sage: len(list(G.spanning_trees(labels=False)))
1168
+ 4
1169
+ """
1170
+ from sage.graphs.graph import Graph
1171
+ if not isinstance(g, Graph):
1172
+ raise ValueError("this method is for undirected graphs only")
1173
+
1174
+ def _recursive_spanning_trees(G, forest, labels):
1175
+ """
1176
+ Return an iterator over all the spanning trees of G containing forest
1177
+ """
1178
+ if not G.is_connected():
1179
+ return
1180
+
1181
+ if G.size() == forest.size():
1182
+ yield forest.copy()
1183
+ else:
1184
+ # Pick an edge e from G-forest
1185
+ for e in G.edge_iterator(labels=labels):
1186
+ if not forest.has_edge(e):
1187
+ break
1188
+
1189
+ # 1) Recursive call with e removed from G
1190
+ G.delete_edge(e)
1191
+ yield from _recursive_spanning_trees(G, forest, labels)
1192
+ G.add_edge(e)
1193
+
1194
+ # 2) Recursive call with e include in forest
1195
+ #
1196
+ # e=xy links the CC (connected component) of forest containing x
1197
+ # with the CC containing y. Any other edge which does that cannot be
1198
+ # added to forest anymore, and B is the list of them
1199
+ c1 = forest.connected_component_containing_vertex(e[0], sort=False)
1200
+ c2 = forest.connected_component_containing_vertex(e[1], sort=False)
1201
+ G.delete_edge(e)
1202
+ B = G.edge_boundary(c1, c2, sort=False)
1203
+ G.add_edge(e)
1204
+
1205
+ # Actual call
1206
+ forest.add_edge(e)
1207
+ G.delete_edges(B)
1208
+ yield from _recursive_spanning_trees(G, forest, labels)
1209
+ G.add_edges(B)
1210
+ forest.delete_edge(e)
1211
+
1212
+ if g.order() and g.is_connected():
1213
+ forest = Graph([g, g.bridges()], format='vertices_and_edges')
1214
+ yield from _recursive_spanning_trees(Graph(g, immutable=False, loops=False), forest, labels)
1215
+
1216
+
1217
+ def edge_disjoint_spanning_trees(G, k, by_weight=False, weight_function=None, check_weight=True):
1218
+ r"""
1219
+ Return `k` edge-disjoint spanning trees of minimum cost.
1220
+
1221
+ This method implements the Roskind-Tarjan algorithm for finding `k`
1222
+ minimum-cost edge-disjoint spanning trees in simple undirected graphs
1223
+ [RT1985]_. When edge weights are taken into account, the algorithm ensures
1224
+ that the sum of the weights of the returned spanning trees is minimized. The
1225
+ time complexity of the algorithm is in `O(k^2n^2)` for the unweighted case
1226
+ and otherwise in `O(m\log{m} + k^2n^2)`.
1227
+
1228
+ This method raises an error if the graph does not contain the requested
1229
+ number of spanning trees.
1230
+
1231
+ INPUT:
1232
+
1233
+ - ``G`` -- a simple undirected graph
1234
+
1235
+ - ``k`` -- the requested number of edge-disjoint spanning trees
1236
+
1237
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges in
1238
+ the graph are weighted, otherwise all edges have weight 1
1239
+
1240
+ - ``weight_function`` -- function (default: ``None``); a function that takes
1241
+ as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``,
1242
+ ``by_weight`` is automatically set to ``True``. If ``None`` and
1243
+ ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not
1244
+ ``None``, else ``1`` as a weight.
1245
+
1246
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
1247
+ that the ``weight_function`` outputs a number for each edge
1248
+
1249
+ EXAMPLES:
1250
+
1251
+ Example from [RT1985]_::
1252
+
1253
+ sage: from sage.graphs.spanning_tree import edge_disjoint_spanning_trees
1254
+ sage: G = Graph({'a': ['b', 'c', 'd', 'e'], 'b': ['c', 'e'], 'c': ['d'], 'd': ['e']})
1255
+ sage: F = edge_disjoint_spanning_trees(G, 2)
1256
+ sage: F
1257
+ [Graph on 5 vertices, Graph on 5 vertices]
1258
+ sage: [f.is_tree() for f in F]
1259
+ [True, True]
1260
+
1261
+ This method raises an error if the graph does not contain the required
1262
+ number of trees::
1263
+
1264
+ sage: edge_disjoint_spanning_trees(G, 3)
1265
+ Traceback (most recent call last):
1266
+ ...
1267
+ EmptySetError: this graph does not contain the required number of trees/arborescences
1268
+
1269
+ A clique of order `n` has `\lfloor n/2 \rfloor` edge disjoint spanning
1270
+ trees::
1271
+
1272
+ sage: for n in range(1, 10):
1273
+ ....: g = graphs.CompleteGraph(n)
1274
+ ....: F = edge_disjoint_spanning_trees(g, n//2)
1275
+
1276
+ The sum of the weights of the returned spanning trees is minimum::
1277
+
1278
+ sage: g = graphs.CompleteGraph(5)
1279
+ sage: for u, v in g.edges(sort=True, labels=False):
1280
+ ....: g.set_edge_label(u, v, 1)
1281
+ sage: g.set_edge_label(0, 1, 33)
1282
+ sage: g.set_edge_label(1, 3, 33)
1283
+ sage: F = edge_disjoint_spanning_trees(g, 2, by_weight=True)
1284
+ sage: sum(F[0].edge_labels()) + sum(F[1].edge_labels())
1285
+ 8
1286
+
1287
+ TESTS:
1288
+
1289
+ A graph with a single vertex has a spanning tree::
1290
+
1291
+ sage: from sage.graphs.spanning_tree import edge_disjoint_spanning_trees
1292
+ sage: edge_disjoint_spanning_trees(Graph(1), 1)
1293
+ [Graph on 1 vertex]
1294
+
1295
+ Check parameter `k`::
1296
+
1297
+ sage: G = graphs.CompleteGraph(4)
1298
+ sage: edge_disjoint_spanning_trees(G, -1)
1299
+ Traceback (most recent call last):
1300
+ ...
1301
+ ValueError: parameter k must be a nonnegative integer
1302
+ sage: edge_disjoint_spanning_trees(G, 0)
1303
+ []
1304
+ sage: edge_disjoint_spanning_trees(G, 1)
1305
+ [Graph on 4 vertices]
1306
+
1307
+ This method is for undirected graphs only::
1308
+
1309
+ sage: edge_disjoint_spanning_trees(DiGraph(), 1)
1310
+ Traceback (most recent call last):
1311
+ ...
1312
+ ValueError: this method is for undirected graphs only
1313
+
1314
+ Check that the method is robust to incomparable vertices::
1315
+
1316
+ sage: # needs sage.numerical.mip
1317
+ sage: G = Graph()
1318
+ sage: G.add_clique([0, 1, 2, 'a', 'b'])
1319
+ sage: F = G.edge_disjoint_spanning_trees(k=2)
1320
+ sage: len(F)
1321
+ 2
1322
+ """
1323
+ if G.is_directed():
1324
+ raise ValueError("this method is for undirected graphs only")
1325
+ G._scream_if_not_simple()
1326
+
1327
+ from sage.categories.sets_cat import EmptySetError
1328
+ from sage.graphs.graph import Graph
1329
+ msg_no_solution = "this graph does not contain the required number of trees/arborescences"
1330
+ if k < 0:
1331
+ raise ValueError("parameter k must be a nonnegative integer")
1332
+ elif not k:
1333
+ return []
1334
+ elif k == 1:
1335
+ E = G.min_spanning_tree()
1336
+ if not E and G.order() != 1:
1337
+ raise EmptySetError(msg_no_solution)
1338
+ return [Graph([G, E], format='vertices_and_edges')]
1339
+ elif k > 1 + min(G.degree()) // 2:
1340
+ raise EmptySetError(msg_no_solution)
1341
+
1342
+ # Initialization of data structures
1343
+
1344
+ # - partition[0] is used to maintain known clumps.
1345
+ # - partition[i], 1 <= i <= k, is used to check if a given edge has both its
1346
+ # endpoints in the same tree of forest Fi.
1347
+ partition = [DisjointSet_of_hashables(G) for _ in range(k + 1)]
1348
+
1349
+ # Mapping from edge to forests:
1350
+ # - edge_index[e] == i if edge e is in Fi, and 0 if not in any Fi
1351
+ # This mapping is sufficient to extract the spanning trees.
1352
+ edge_index = {frozenset(e): 0 for e in G.edge_iterator(labels=False)}
1353
+
1354
+ # Data structure to maintain the edge sets of each forest.
1355
+ # This is not a requirement of the algorithm as we can use the mapping
1356
+ # edge_index. However, it is convenient to maintain the forest as graphs to
1357
+ # simplify some operations.
1358
+ H = Graph([G, []], format='vertices_and_edges')
1359
+ F = [H.copy() for _ in range(k + 1)]
1360
+
1361
+ # We consider the edges by increasing weight
1362
+ by_weight, weight_function = G._get_weight_function(by_weight=by_weight,
1363
+ weight_function=weight_function,
1364
+ check_weight=check_weight)
1365
+ if not by_weight:
1366
+ weight_function = None
1367
+
1368
+ for x, y, _ in G.edges(sort=by_weight, key=weight_function):
1369
+ # {x, y} is edge e0 in the algorithm
1370
+
1371
+ if partition[0].find(x) == partition[0].find(y):
1372
+ # x and y are in a same clump. That is x and y are in a same tree
1373
+ # in every forest Fi. We proceed with the next edge.
1374
+ continue
1375
+
1376
+ # else, we apply the labeling algorithm
1377
+
1378
+ # Label assigned to each edge by the labeling algorithm
1379
+ edge_label = {}
1380
+
1381
+ # We use a queue of edges
1382
+ queue = [(x, y)]
1383
+ queue_begin = 0
1384
+ queue_end = 1
1385
+
1386
+ # We find the tree Ti in Fi containing x, root Ti at x and
1387
+ # compute the parent pi(v) of every vertex in Ti
1388
+ p = [{x: x} for _ in range(k + 1)]
1389
+ for i in range(1, k + 1):
1390
+ # BFS will consider only vertices of the tree Ti of Fi containing x
1391
+ for u, v in F[i].breadth_first_search(x, edges=True):
1392
+ p[i][v] = u
1393
+
1394
+ # and we search for an augmenting sequence
1395
+ augmenting_sequence_found = False
1396
+ while queue_begin < queue_end:
1397
+ e = queue[queue_begin]
1398
+ queue_begin += 1
1399
+ fe = frozenset(e)
1400
+ i = (edge_index[fe] % k) + 1
1401
+ v, w = e
1402
+ if partition[i].find(v) != partition[i].find(w):
1403
+ # v and w are in different subtrees of Fi. We have detected an
1404
+ # augmenting sequence since we can join the two subtrees.
1405
+ augmenting_sequence_found = True
1406
+ break
1407
+ else:
1408
+ # One of v and w is in the subtree of labeled edges in Fi
1409
+ if v == x or (v in p[i] and frozenset((v, p[i][v])) in edge_label):
1410
+ u = w
1411
+ else:
1412
+ u = v
1413
+
1414
+ # Let F(e) be the unique path joining v and w.
1415
+ # We find the unlabeled edges of Fi(e) by ascending through the
1416
+ # tree one vertex at a time from z toward x, until reaching
1417
+ # either x or a previously labeled edge.
1418
+
1419
+ # Stack of edges to be labeled
1420
+ edges_to_label = []
1421
+ while u != x and (u in p[i] and frozenset((u, p[i][u])) not in edge_label):
1422
+ edges_to_label.append((u, p[i][u]))
1423
+ u = p[i][u]
1424
+
1425
+ # We now label edges
1426
+ while edges_to_label:
1427
+ ep = edges_to_label.pop()
1428
+ edge_label[frozenset(ep)] = fe
1429
+ queue.append(ep)
1430
+ queue_end += 1
1431
+
1432
+ if augmenting_sequence_found:
1433
+ # We perform the corresponding augmentation
1434
+ partition[i].union(v, w)
1435
+
1436
+ while fe in edge_label:
1437
+ F[edge_index[fe]].delete_edge(fe)
1438
+ F[i].add_edge(fe)
1439
+ e, edge_index[fe], i = edge_label[fe], i, edge_index[fe]
1440
+ fe = frozenset(e)
1441
+
1442
+ # Finally, add edge e = e0 = (x, y) to Fi
1443
+ F[i].add_edge(e)
1444
+ edge_index[fe] = i
1445
+
1446
+ else:
1447
+ # x and y are in a same tree in every Fi, so in a same clump
1448
+ partition[0].union(x, y)
1449
+
1450
+ res = [F[i] for i in range(1, k + 1) if F[i].size() == G.order() - 1]
1451
+ if len(res) != k:
1452
+ raise EmptySetError(msg_no_solution)
1453
+
1454
+ for f in res:
1455
+ for u, v in f.edges(sort=False, labels=False):
1456
+ f.set_edge_label(u, v, G.edge_label(u, v))
1457
+ return res