|
|
""" |
|
|
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 |
|
|
|
|
|
|
|
|
@nx._dispatch(graphs=None) |
|
|
def nonisomorphic_trees(order, create="graph"): |
|
|
"""Returns a list of nonisomorphic trees |
|
|
|
|
|
Parameters |
|
|
---------- |
|
|
order : int |
|
|
order of the desired tree(s) |
|
|
|
|
|
create : graph or matrix (default="Graph) |
|
|
If graph is selected a list of trees will be returned, |
|
|
if matrix is selected a list of adjacency matrix will |
|
|
be returned |
|
|
|
|
|
Returns |
|
|
------- |
|
|
G : List of NetworkX Graphs |
|
|
|
|
|
M : List of Adjacency matrices |
|
|
|
|
|
References |
|
|
---------- |
|
|
|
|
|
""" |
|
|
|
|
|
if order < 2: |
|
|
raise ValueError |
|
|
|
|
|
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": |
|
|
yield _layout_to_matrix(layout) |
|
|
layout = _next_rooted_tree(layout) |
|
|
|
|
|
|
|
|
@nx._dispatch(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.""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
left, rest = _split_tree(candidate) |
|
|
|
|
|
|
|
|
|
|
|
left_height = max(left) |
|
|
rest_height = max(rest) |
|
|
valid = rest_height >= left_height |
|
|
|
|
|
if valid and rest_height == left_height: |
|
|
|
|
|
|
|
|
if len(left) > len(rest): |
|
|
valid = False |
|
|
|
|
|
|
|
|
elif len(left) == len(rest) and left > rest: |
|
|
valid = False |
|
|
|
|
|
if valid: |
|
|
return candidate |
|
|
else: |
|
|
|
|
|
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 |
|
|
|