Как сгенерировать все деревья, имеющие n-узлы и глубину m-уровня? Коэффициент ветвления является переменным и не должен быть постоянным внутри самого дерева
Я полагаю, довольно простой вопрос.
Например, алгоритм уровня 5 с 5 узлами исключит
A
|
B
|
C
|
D
|
E
но нет
A
/|\
B C D
\
E
что так же, как (по крайней мере для меня)
A
/|\
D C B
/
E
Например, алгоритм 3-Node 2-Level генерирует только следующее:
(1) (2) (3)
A B C
/ \ or / \ or / \
B C A C A B
Обратите внимание, что я считаю дерево ниже таким же, как (2) выше:
B
/ \
C A
1 ответ
Базовая структура решения будет рекурсивной, хотя точные детали меняются в зависимости от классов точной эквивалентности, которые вы принимаете для генерации. Здесь вам нужно будет рассмотреть такие вопросы, как, помечены ли узлы или нет, и упорядочены ли дочерние узлы или нет. Каждый из этих вариантов определяет очень разные, но одинаково интересные проблемы поколения.
Основной алгоритм состоит в том, чтобы выяснить перечисление возможных наборов дочерних элементов, подчиняющихся классам эквивалентности, определенным для конкретной задачи, путем создания только канонической записи для каждого класса эквивалентности. Например, для немаркированных деревьев с упорядоченными дочерними элементами мы могли бы начать с перечисления всех упорядоченных разделов числа узлов; для данного раздела мы сформируем декартово произведение всех возможных возможных поддеревьев указанных размеров. Если бы мы не заботились о порядке детей, нам нужно было бы найти какой-нибудь способ составления канонических списков детей: мы могли бы сначала отсортировать поддеревья по общему размеру, но затем нам нужен канонический порядок для двух поддеревьев. того же размера. Это сложно, но есть решения.
Я понимаю, что ваша проблема состоит не только в количестве узлов, но и в специфических метках узлов, и поэтому два дерева одинаковой формы, но с разными метками должны рассматриваться как разные. Однако вы хотите, чтобы порядок детей не имел отношения к сравнению. Тот факт, что все узлы имеют уникальные метки, делает проблему вполне решаемой: мы можем перечислять непустые подмножества доступных меток, потому что мы знаем, что деревья, сгенерированные с одним подмножеством меток, должны отличаться от деревьев, сгенерированных с другим подмножеством., Поскольку сами поддеревья неупорядочены, мы можем канонизировать, сохранив отсортированные корни поддеревьев (любое упорядочение так же хорошо, как и любое другое).
Таким образом, мы в конечном итоге с процедурой GenTrees(R, D, N)
, где R
это корневая метка, D
набор меток-потомков, и N
максимальная глубина дерева, которая может быть определена следующим образом:
Если
D
пусто, возвращает одноэлементное множество с деревом, состоящим только из указанного корневого узла.(Для эффективности) Если
N
равен 1, возвращает одноэлементное множество с деревом, состоящим из указанного корневого узла с оставшимися узлами в виде листьев. (Менее эффективным шагом является "ЕслиN
0, вернуть пустой набор."Но лучше ярлык, проверивN == 1
.)В противном случае начните с пустого набора возможных деревьев и
Для каждого непустого подмножества
S
изD
(это дочерние элементы корневого узла):За каждый заказанный раздел
P
изD - S
в|S|
подмножества (это оставшиеся потомки каждого поддерева):- Добавить к возвращаемому множеству все деревья с корнем
R
чьи дети вGenTrees(Si, Pi, N-1)
, (Это декартово произведение. Здесь порядокS
произвольно, но должно быть согласованным; сортировка меток в порядке возрастания была бы очевидным решением.)
- Добавить к возвращаемому множеству все деревья с корнем
Чтобы немного протестировать вышесказанное, я написал пример реализации на Python. Он не пытается быть эффективным, и в основном это транскрипция вышеупомянутого с использованием генераторов Python. (Преимущество генераторов в том, что нам не нужно заполнять память миллионами возможных деревьев; мы просто генерируем по одному за раз.)
В случае, если это яснее, чем словоблудие, вот оно:
from functools import reduce
from itertools import product
class tree(object):
def __init__(self, root, kids=()):
self.root = root
self.kids = tuple(kids)
def __str__(self):
'''Produce an S-expr for the tree.'''
if self.kids:
return "(%s %s)" % (self.root,
' '.join(str(kid) for kid in self.kids))
else:
return self.root
def append(part, wherewhat):
part[wherewhat[0]].append(wherewhat[1])
return part
def gentrees(root, labels, N):
if not labels or N <= 1:
yield tree(root, labels if N else ())
else:
for p in product((0,1), repeat = len(labels)):
if any(p):
nexts, roots = reduce(append, zip(p, labels), ([], []))
for q in product(range(len(roots)), repeat = len(nexts)):
kidsets = reduce(append, zip(q, nexts), tuple([] for i in roots))
yield from (tree(root, kids)
for kids in product(*(gentrees(root, rest, N-1)
for root, rest in zip(roots, kidsets))))
def alltrees(labels, N):
for i, root in enumerate(labels):
yield from gentrees(root, labels[:i] + labels[i+1:], N)
Последняя функция перебирает все возможные корни и вызывает гентри с каждым из них. Вот пример выходных данных для трех узлов и максимальной глубины 2 (эта функция считает глубину в ссылках, а не в узлах):
>>> print('\n'.join(map(str,alltrees("ABC",2))))
(A (C B))
(A (B C))
(A B C)
(B (C A))
(B (A C))
(B A C)
(C (B A))
(C (A B))
(C A B)
Стоит отметить, что количество сгенерированных деревьев увеличивается экспоненциально с количеством меток узлов. Если вы не ограничиваете себя очень маленькими деревьями, вам, вероятно, следует попытаться по возможности сократить рекурсию поколения (сверху вниз или снизу вверх или и то, и другое).