У меня есть несколько двоичных деревьев, хранящихся в виде массива. В каждом слоте есть либо ноль (или ноль; выберите ваш язык), либо фиксированный кортеж, в котором хранятся два числа: индексы двух «детей». Ни у одного узла не будет только одного дочернего элемента - ни одного, ни двух.
Думайте о каждом слоте как о двоичном узле, который хранит только указатели на его дочерние элементы, а не присуще значение.
Возьмем эту систему двоичных деревьев:
0 1
/ \ / \
2 3 4 5
/ \ / \
6 7 8 9
/ \
10 11
Связанный массив будет:
0 1 2 3 4 5 6 7 8 9 10 11
[ [2,3] , [4,5] , [6,7] , nil , nil , [8,9] , nil , [10,11] , nil , nil , nil , nil ]
Я уже написал простые функции для поиска прямых родителей узлов (просто путем поиска спереди, пока не найдется узел, содержащий дочерний элемент)
Кроме того, давайте скажем, что в соответствующие моменты времени оба дерева находятся на глубине от нескольких до нескольких тысяч уровней.
Я бы хотел найти функцию
P(m,n)
чтобы найти наименьшего общего предка m и n - для более формального определения, LCA определяется как «самый низкий» или самый глубокий узел, в котором m и n являются потомками (дочерние элементы или дочерние элементы дочерних элементов и т. Д.). .). Если его нет, nil будет верным возвращением.
Несколько примеров, приведенных для нашего данного дерева:
P( 6,11) # => 2
P( 3,10) # => 0
P( 8, 6) # => nil
P( 2,11) # => 2
Основной метод, который мне удалось найти, - это метод, использующий трассировку Эйлера, которая превращает данное дерево (добавление узла A в качестве невидимого родителя 0 и 1 со «значением» -1) в :
A-0-2-6-2-7-10-7-11-7-2-0-3-0-A-1-4-1-5-8-5-9-5-1-A
И из этого просто найдите узел между заданными вами m и n, который имеет наименьшее число; Например, чтобы найти P (6,11), найдите 6 и 11 на трассе. Число между ними, которое является самым низким, равно 2, и это ваш ответ. Если между ними находится A (-1), вернуть nil.
-- Calculating P(6,11) --
A-0-2-6-2-7-10-7-11-7-2-0-3-0-A-1-4-1-5-8-5-9-5-1-A
^ ^ ^
| | |
m lowest n
К сожалению, я верю, что нахождение следа Эйлера на дереве, который может иметь глубину несколько тысяч уровней, немного обременительно для машины ... и потому что мое дерево постоянно меняется на протяжении всего курса программирования, каждый раз, когда я хотел найти LCA, мне приходилось пересчитывать трассу Эйлера и каждый раз удерживать ее в памяти.
Есть ли более эффективный способ использования памяти, если я использую платформу? Тот, который может повторяться вверх? Один из способов, который я мог бы придумать, - это «посчитать» генерацию / глубину обоих узлов и подняться на самый нижний узел, пока он не совпадет с глубиной самого высокого, и увеличивать оба, пока они не найдут кого-то похожего.
Но для этого потребуется подняться с уровня, скажем, 3025, обратно на 0, дважды , чтобы посчитать генерацию, и сначала использовать крайне неэффективный алгоритм подъема, а затем восхождение наверх.
Есть ли другие лучшие способы?
Разъяснения
При построении этой системы, у каждого ребенка будет номер больше, чем у его родителей .
Это не гарантирует, что если n в поколении X, то в поколении (X-1) нет узлов, которые больше n. Например:
0
/ \
/ \
/ \
1 2 6
/ \ / \ / \
2 3 9 10 7 8
/ \ / \
4 5 11 12
- допустимая древовидная система.
Кроме того, артефакт способа построения деревьев заключается в том, что два непосредственных потомка одного и того же родителя всегда будут последовательно пронумерованы.