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.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- 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
|