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