Source code for networkx.generators.nonisomorphic_trees

"""
Implementation of the Wright, Richmond, Odlyzko and McKay (WROM)
algorithm for the enumeration of all non-isomorphic free trees of a
given order.  Rooted trees are represented by level sequences, i.e.,
lists in which the i-th element specifies the distance of vertex i to
the root.

"""

__all__ = ["nonisomorphic_trees", "number_of_nonisomorphic_trees"]

import networkx as nx


[docs] @nx._dispatchable(graphs=None, returns_graph=True) def nonisomorphic_trees(order, create="graph"): """Generates lists of nonisomorphic trees Parameters ---------- order : int order of the desired tree(s) create : one of {"graph", "matrix"} (default="graph") If ``"graph"`` is selected a list of ``Graph`` instances will be returned, if matrix is selected a list of adjacency matrices will be returned. .. deprecated:: 3.3 The `create` argument is deprecated and will be removed in NetworkX version 3.5. In the future, `nonisomorphic_trees` will yield graph instances by default. To generate adjacency matrices, call ``nx.to_numpy_array`` on the output, e.g.:: [nx.to_numpy_array(G) for G in nx.nonisomorphic_trees(N)] Yields ------ list A list of nonisomorphic trees, in one of two formats depending on the value of the `create` parameter: - ``create="graph"``: yields a list of `networkx.Graph` instances - ``create="matrix"``: yields a list of list-of-lists representing adjacency matrices """ if order < 2: raise ValueError # start at the path graph rooted at its center layout = list(range(order // 2 + 1)) + list(range(1, (order + 1) // 2)) while layout is not None: layout = _next_tree(layout) if layout is not None: if create == "graph": yield _layout_to_graph(layout) elif create == "matrix": import warnings warnings.warn( ( "\n\nThe 'create=matrix' argument of nonisomorphic_trees\n" "is deprecated and will be removed in version 3.5.\n" "Use ``nx.to_numpy_array`` to convert graphs to adjacency " "matrices, e.g.::\n\n" " [nx.to_numpy_array(G) for G in nx.nonisomorphic_trees(N)]" ), category=DeprecationWarning, stacklevel=2, ) yield _layout_to_matrix(layout) layout = _next_rooted_tree(layout)
[docs] @nx._dispatchable(graphs=None) def number_of_nonisomorphic_trees(order): """Returns the number of nonisomorphic trees Parameters ---------- order : int order of the desired tree(s) Returns ------- length : Number of nonisomorphic graphs for the given order References ---------- """ return sum(1 for _ in nonisomorphic_trees(order))
def _next_rooted_tree(predecessor, p=None): """One iteration of the Beyer-Hedetniemi algorithm.""" if p is None: p = len(predecessor) - 1 while predecessor[p] == 1: p -= 1 if p == 0: return None q = p - 1 while predecessor[q] != predecessor[p] - 1: q -= 1 result = list(predecessor) for i in range(p, len(result)): result[i] = result[i - p + q] return result def _next_tree(candidate): """One iteration of the Wright, Richmond, Odlyzko and McKay algorithm.""" # valid representation of a free tree if: # there are at least two vertices at layer 1 # (this is always the case because we start at the path graph) left, rest = _split_tree(candidate) # and the left subtree of the root # is less high than the tree with the left subtree removed left_height = max(left) rest_height = max(rest) valid = rest_height >= left_height if valid and rest_height == left_height: # and, if left and rest are of the same height, # if left does not encompass more vertices if len(left) > len(rest): valid = False # and, if they have the same number or vertices, # if left does not come after rest lexicographically elif len(left) == len(rest) and left > rest: valid = False if valid: return candidate else: # jump to the next valid free tree p = len(left) new_candidate = _next_rooted_tree(candidate, p) if candidate[p] > 2: new_left, new_rest = _split_tree(new_candidate) new_left_height = max(new_left) suffix = range(1, new_left_height + 2) new_candidate[-len(suffix) :] = suffix return new_candidate def _split_tree(layout): """Returns a tuple of two layouts, one containing the left subtree of the root vertex, and one containing the original tree with the left subtree removed.""" one_found = False m = None for i in range(len(layout)): if layout[i] == 1: if one_found: m = i break else: one_found = True if m is None: m = len(layout) left = [layout[i] - 1 for i in range(1, m)] rest = [0] + [layout[i] for i in range(m, len(layout))] return (left, rest) def _layout_to_matrix(layout): """Create the adjacency matrix for the tree specified by the given layout (level sequence).""" result = [[0] * len(layout) for i in range(len(layout))] stack = [] for i in range(len(layout)): i_level = layout[i] if stack: j = stack[-1] j_level = layout[j] while j_level >= i_level: stack.pop() j = stack[-1] j_level = layout[j] result[i][j] = result[j][i] = 1 stack.append(i) return result def _layout_to_graph(layout): """Create a NetworkX Graph for the tree specified by the given layout(level sequence)""" G = nx.Graph() stack = [] for i in range(len(layout)): i_level = layout[i] if stack: j = stack[-1] j_level = layout[j] while j_level >= i_level: stack.pop() j = stack[-1] j_level = layout[j] G.add_edge(i, j) stack.append(i) return G