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