coding.py 13 KB

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