-
Notifications
You must be signed in to change notification settings - Fork 0
/
prufer.py
407 lines (328 loc) · 13 KB
/
prufer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# -*- encoding: utf-8 -*-
#
# coding.py - functions for encoding and decoding trees as sequences
#
# Copyright 2015, 2016 NetworkX developers.
#
# This file is part of NetworkX.
#
# NetworkX is distributed under a BSD license; see LICENSE.txt for more
# information.
"""Functions for encoding and decoding trees.
Since a tree is a highly restricted form of graph, it can be represented
concisely in several ways. This module includes functions for encoding
and decoding trees in the form of nested tuples and Prüfer
sequences. The former requires a rooted tree, whereas the latter can be
applied to unrooted trees. Furthermore, there is a bijection from Prüfer
sequences to labeled trees.
"""
from collections import Counter
from itertools import chain
import networkx as nx
from networkx.utils import not_implemented_for
__all__ = ['from_nested_tuple', 'from_prufer_sequence', 'NotATree',
'to_nested_tuple', 'to_prufer_sequence']
class NotATree(nx.NetworkXException):
"""Raised when a function expects a tree (that is, a connected
undirected graph with no cycles) but gets a non-tree graph as input
instead.
"""
@not_implemented_for('directed')
def to_nested_tuple(T, root, canonical_form=False):
"""Returns a nested tuple representation of the given tree.
The nested tuple representation of a tree is defined
recursively. The tree with one node and no edges is represented by
the empty tuple, ``()``. A tree with ``k`` subtrees is represented
by a tuple of length ``k`` in which each element is the nested tuple
representation of a subtree.
Parameters
----------
T : NetworkX graph
An undirected graph object representing a tree.
root : node
The node in ``T`` to interpret as the root of the tree.
canonical_form : bool
If ``True``, each tuple is sorted so that the function returns
a canonical form for rooted trees. This means "lighter" subtrees
will appear as nested tuples before "heavier" subtrees. In this
way, each isomorphic rooted tree has the same nested tuple
representation.
Returns
-------
tuple
A nested tuple representation of the tree.
Notes
-----
This function is *not* the inverse of :func:`from_nested_tuple`; the
only guarantee is that the rooted trees are isomorphic.
See also
--------
from_nested_tuple
to_prufer_sequence
Examples
--------
The tree need not be a balanced binary tree::
>>> T = nx.Graph()
>>> T.add_edges_from([(0, 1), (0, 2), (0, 3)])
>>> T.add_edges_from([(1, 4), (1, 5)])
>>> T.add_edges_from([(3, 6), (3, 7)])
>>> root = 0
>>> nx.to_nested_tuple(T, root)
(((), ()), (), ((), ()))
Continuing the above example, if ``canonical_form`` is ``True``, the
nested tuples will be sorted::
>>> nx.to_nested_tuple(T, root, canonical_form=True)
((), ((), ()), ((), ()))
Even the path graph can be interpreted as a tree::
>>> T = nx.path_graph(4)
>>> root = 0
>>> nx.to_nested_tuple(T, root)
((((),),),)
"""
def _make_tuple(T, root, _parent):
"""Recursively compute the nested tuple representation of the
given rooted tree.
``_parent`` is the parent node of ``root`` in the supertree in
which ``T`` is a subtree, or ``None`` if ``root`` is the root of
the supertree. This argument is used to determine which
neighbors of ``root`` are children and which is the parent.
"""
# Get the neighbors of `root` that are not the parent node. We
# are guaranteed that `root` is always in `T` by construction.
children = set(T[root]) - {_parent}
if len(children) == 0:
return ()
nested = (_make_tuple(T, v, root) for v in children)
if canonical_form:
nested = sorted(nested)
return tuple(nested)
# Do some sanity checks on the input.
if not nx.is_tree(T):
raise nx.NotATree('provided graph is not a tree')
if root not in T:
raise nx.NodeNotFound('Graph {} contains no node {}'.format(T, root))
return _make_tuple(T, root, None)
def from_nested_tuple(sequence, sensible_relabeling=False):
"""Returns the rooted tree corresponding to the given nested tuple.
The nested tuple representation of a tree is defined
recursively. The tree with one node and no edges is represented by
the empty tuple, ``()``. A tree with ``k`` subtrees is represented
by a tuple of length ``k`` in which each element is the nested tuple
representation of a subtree.
Parameters
----------
sequence : tuple
A nested tuple representing a rooted tree.
sensible_relabeling : bool
Whether to relabel the nodes of the tree so that nodes are
labeled in increasing order according to their breadth-first
search order from the root node.
Returns
-------
NetworkX graph
The tree corresponding to the given nested tuple, whose root
node is node 0. If ``sensible_labeling`` is ``True``, nodes will
be labeled in breadth-first search order starting from the root
node.
Notes
-----
This function is *not* the inverse of :func:`to_nested_tuple`; the
only guarantee is that the rooted trees are isomorphic.
See also
--------
to_nested_tuple
from_prufer_sequence
Examples
--------
Sensible relabeling ensures that the nodes are labeled from the root
starting at 0::
>>> balanced = (((), ()), ((), ()))
>>> T = nx.from_nested_tuple(balanced, sensible_relabeling=True)
>>> edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
>>> all((u, v) in T.edges() or (v, u) in T.edges() for (u, v) in edges)
True
"""
def _make_tree(sequence):
"""Recursively creates a tree from the given sequence of nested
tuples.
This function employs the :func:`~networkx.tree.join` function
to recursively join subtrees into a larger tree.
"""
# The empty sequence represents the empty tree, which is the
# (unique) graph with a single node. We mark the single node
# with an attribute that indicates that it is the root of the
# graph.
if len(sequence) == 0:
return nx.empty_graph(1)
# For a nonempty sequence, get the subtrees for each child
# sequence and join all the subtrees at their roots. After
# joining the subtrees, the root is node 0.
return nx.tree.join([(_make_tree(child), 0) for child in sequence])
# Make the tree and remove the `is_root` node attribute added by the
# helper function.
T = _make_tree(sequence)
if sensible_relabeling:
# Relabel the nodes according to their breadth-first search
# order, starting from the root node (that is, the node 0).
bfs_nodes = chain([0], (v for u, v in nx.bfs_edges(T, 0)))
labels = {v: i for i, v in enumerate(bfs_nodes)}
# We would like to use `copy=False`, but `relabel_nodes` doesn't
# allow a relabel mapping that can't be topologically sorted.
T = nx.relabel_nodes(T, labels)
return T
@not_implemented_for('directed')
def to_prufer_sequence(T):
"""Returns the Prüfer sequence of the given tree.
A *Prüfer sequence* is a list of *n* - 2 numbers between 0 and
*n* - 1, inclusive. The tree corresponding to a given Prüfer
sequence can be recovered by repeatedly joining a node in the
sequence with a node with the smallest potential degree according to
the sequence.
Parameters
----------
T : NetworkX graph
An undirected graph object representing a tree.
Returns
-------
list
The Prüfer sequence of the given tree.
Raises
------
NetworkXPointlessConcept
If the number of nodes in `T` is less than two.
NotATree
If `T` is not a tree.
KeyError
If the set of nodes in `T` is not {0, …, *n* - 1}.
Notes
-----
There is a bijection from labeled trees to Prüfer sequences. This
function is the inverse of the :func:`from_prufer_sequence`
function.
Sometimes Prüfer sequences use nodes labeled from 1 to *n* instead
of from 0 to *n* - 1. This function requires nodes to be labeled in
the latter form. You can use :func:`~networkx.relabel_nodes` to
relabel the nodes of your tree to the appropriate format.
This implementation is from [1]_ and has a running time of
:math:`O(n \log n)`.
See also
--------
to_nested_tuple
from_prufer_sequence
References
----------
.. [1] Wang, Xiaodong, Lei Wang, and Yingjie Wu.
"An optimal algorithm for Prufer codes."
*Journal of Software Engineering and Applications* 2.02 (2009): 111.
<http://dx.doi.org/10.4236/jsea.2009.22016>
Examples
--------
There is a bijection between Prüfer sequences and labeled trees, so
this function is the inverse of the :func:`from_prufer_sequence`
function::
>>> edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)]
>>> tree = nx.Graph(edges)
>>> sequence = nx.to_prufer_sequence(tree)
>>> sequence
[3, 3, 3, 4]
>>> tree2 = nx.from_prufer_sequence(sequence)
>>> list(tree2.edges()) == edges
True
"""
# Perform some sanity checks on the input.
n = len(T)
if n < 2:
msg = 'Prüfer sequence undefined for trees with fewer than two nodes'
raise nx.NetworkXPointlessConcept(msg)
if not nx.is_tree(T):
raise nx.NotATree('provided graph is not a tree')
if set(T) != set(range(n)):
raise KeyError('tree must have node labels {0, ..., n - 1}')
degree = dict(T.degree())
def parents(u):
return next(v for v in T[u] if degree[v] > 1)
index = u = min(k for k in range(n) if degree[k] == 1)
result = []
for i in range(n - 2):
v = parents(u)
result.append(v)
degree[v] -= 1
if v < index and degree[v] == 1:
u = v
else:
index = u = min(k for k in range(index + 1, n) if degree[k] == 1)
return result
def from_prufer_sequence(sequence):
"""Returns the tree corresponding to the given Prüfer sequence.
A *Prüfer sequence* is a list of *n* - 2 numbers between 0 and
*n* - 1, inclusive. The tree corresponding to a given Prüfer
sequence can be recovered by repeatedly joining a node in the
sequence with a node with the smallest potential degree according to
the sequence.
Parameters
----------
sequence : list
A Prüfer sequence, which is a list of *n* - 2 integers between
zero and *n* - 1, inclusive.
Returns
-------
NetworkX graph
The tree corresponding to the given Prüfer sequence.
Notes
-----
There is a bijection from labeled trees to Prüfer sequences. This
function is the inverse of the :func:`from_prufer_sequence` function.
Sometimes Prüfer sequences use nodes labeled from 1 to *n* instead
of from 0 to *n* - 1. This function requires nodes to be labeled in
the latter form. You can use :func:`networkx.relabel_nodes` to
relabel the nodes of your tree to the appropriate format.
This implementation is from [1]_ and has a running time of
:math:`O(n \log n)`.
References
----------
.. [1] Wang, Xiaodong, Lei Wang, and Yingjie Wu.
"An optimal algorithm for Prufer codes."
*Journal of Software Engineering and Applications* 2.02 (2009): 111.
<http://dx.doi.org/10.4236/jsea.2009.22016>
See also
--------
from_nested_tuple
to_prufer_sequence
Examples
--------
There is a bijection between Prüfer sequences and labeled trees, so
this function is the inverse of the :func:`to_prufer_sequence`
function::
>>> edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)]
>>> tree = nx.Graph(edges)
>>> sequence = nx.to_prufer_sequence(tree)
>>> sequence
[3, 3, 3, 4]
>>> tree2 = nx.from_prufer_sequence(sequence)
>>> list(tree2.edges()) == edges
True
"""
n = len(sequence) + 2
# `degree` stores the remaining degree (plus one) for each node. The
# degree of a node in the decoded tree is one more than the number
# of times it appears in the code.
degree = Counter(chain(sequence, range(n)))
T = nx.empty_graph(n)
# `not_orphaned` is the set of nodes that have a parent in the
# tree. After the loop, there should be exactly two nodes that are
# not in this set.
not_orphaned = set()
index = u = min(k for k in range(n) if degree[k] == 1)
for v in sequence:
T.add_edge(u, v)
not_orphaned.add(u)
degree[v] -= 1
if v < index and degree[v] == 1:
u = v
else:
index = u = min(k for k in range(index + 1, n) if degree[k] == 1)
# At this point, there must be exactly two orphaned nodes; join them.
orphans = set(T) - not_orphaned
u, v = orphans
T.add_edge(u, v)
return T