Постепенно сохраняйте путь от корневого узла к узлу многолучевого дерева во время вставки, чтобы операция хранения не имела сложности O(n)
Я хотел бы спросить, знает ли кто-нибудь эффективный способ сохранить путь от корневого узла к новому узлу многострочного дерева во время вставки нового узла. Например, если у меня есть следующее дерево:
Для каждого узла в настоящее время я сохраняю массив пути к узлу от корневого узла во время вставки следующим образом, назначая уникальный int
Удостоверение личности каждого ребенка на одной глубине:
Root node -> [1]
Depth 1, child 1 of root -> [1, 1]
Depth 1, child 2 of root -> [1, 2]
Depth 2, child 1 of parent 1 -> [1, 1, 1]
Depth 2, child 2 of parent 1 -> [1, 1, 2]
Depth 2, child 3 of parent 1 -> [1, 1, 3]
Depth 2, child 1 of parent 2 -> [1, 2, 4]
Depth 2, child 2 of parent 2 -> [1, 2, 5]
Depth 3, child 1 of parent 3 -> [1, 1, 3, 1]
...
Если я сейчас вставлю новый узел из конечного узла 1
на глубине 3, я должен был бы создать новый массив пути для него, хранящий все узлы родителя 1
(т.е. [1, 1, 3, 1]
) плюс новый идентификатор ребенка, который 1
для первого ребенка:
Depth 4, child 1 of parent 1 -> [1, 1, 3, 1, 1]
Поскольку мое дерево сильно растет в росте (число дочерних элементов на глубину относительно мало, но глубина может быть высокой), медленной частью этого алгоритма будет процесс восстановления массива. Просто представьте дерево глубины 1.000.000
, если я вставлю новый узел из узла глубины 1.000.000
Я должен был бы создать новый массив для этого нового узла, хранящего все 1.000.001
Идентификаторы родителя плюс добавление идентификатора нового узла:
Depth 1.000.001, child 1 of parent x -> [...1 million and one IDs... , 1]
Есть ли более эффективный способ сохранить путь на каждом узле во время вставки узла?
Мне в основном нужно это, чтобы определить, является ли какой-либо данный узел дочерним по отношению к возможному родительскому узлу в дереве, и, поскольку у меня есть путь, сохраненный в каждом узле, я легко могу это сделать, проверив массив путей дочернего элемента, например:
// Ex. 1
Is node 4 of depth 2 the parent/ancestor of node 1 of depth 3?
node 1 of depth 3 has the following path array: pathArray = [1, 1, 3, 1]
Its ancestor ID on depth 2 is: pathArray[2] -> 3
3 != 4 and therefore I know that node 4 of depth 2
is not a parent of node 1 of depth 3.
// Ex. 2
Is node 1 of depth 1 the parent/ancestor of node 1 of depth 3?
node 1 of depth 3 has the following path array: pathArray = [1, 1, 3, 1]
Its ancestor ID on depth 1 is: pathArray[1] -> 1
1 == 1 and therefore I know that node 1 of depth 1
is a parent of node 1 of depth 3.
Эта операция поиска будет быстрой, проблема заключается в создании массива пути по мере того, как дерево углубляется.
Мы ценим любые предложения.
Спасибо за внимание.
3 ответа
Прямо сейчас ваше решение имеет O(1)
время поиска, O(h)
введите время и O(n^2)
космическая выпуклость, где n
это количество узлов и h
это высота, которая не более n
,
Вы можете достичь компромисса с O(log n)
уважать, O((log n)^2)
вставить и O(n log n)
пространство следующим образом:
Пусть каждый узел хранит один указатель перехода на каждого из своих предков с расстояния 1 (его родитель), 2 (дедушка), 4 (дедушка и дедушка), 8, 16 и т. Д., Пока корень не будет достигнут или пройден. Максимальное расстояние от любого узла до корня n
так что для каждого узла вы храните O(log n)
прыгать-указатели. Поскольку вы делаете это для каждого узла, общая сложность пространства O(n log n)
,
Отвечая на вопрос о том, y
является предком x
тривиально, если y
не имеет более низкой глубины, чем x
, Назовите глубину узлов dy
а также dx
, Вы знаете, что если y
является предком x
тогда это dx-dy
предок x
, То есть если dy = 5
а также dx = 17
Знаете, если y
является x
предок, то это 17 - 5
уровни выше x
,
Следовательно, вы можете выполнять поиск, рекурсивно прыгая на максимально возможное расстояние вверх по дереву из x
не выходя за пределы целевого предка. Например, если вы начинаете с глубины 16 и хотите найти предка на глубине 6, вам интересны предки на 10 уровней выше. Вы не можете прыгнуть на 16 уровней выше, так как это приведет к превышению заданного предка, поэтому вместо этого вы прыгаете на 8. Теперь вы находитесь на глубине 16-8=8, а оставшееся расстояние до целевого предка, которое равно 6, равно 2. Так как есть указатель, который поднимается ровно на два шага вверх, вы следуете этому и достигли целевой предок.
Каждый раз, когда вы следите за указателем вверх по дереву, вы получаете по крайней мере половину пути к своей цели, поэтому максимальное количество указателей, за которыми вы можете следовать, O(log n)
,
При вставке узла e
как ребенок другого узла x
вы можете построить e
прыжковые указатели, находя x
предки с расстояния 1, 3, 7, 15 и т. д. (с e
на один уровень дальше от всех этих, чем x
является). Есть O(log n)
такие поиски. Как мы уже говорили выше, каждый из поисков занимает O(log n)
время. Таким образом, общая сумма O((log n)^2)
,
Эту операцию можно было бы сделать еще быстрее, сохранив некоторую дополнительную информацию, но я не могу понять, как именно сейчас.
ПРИМЕЧАНИЕ. Эта идея на самом деле является частью классического решения проблемы предка уровня. Классическое решение позволяет искать, как вы описали их в O(1)
время, сохраняя при этом пространство всей структуры данных O(n)
, Однако структура данных является статической, поэтому в решении не указано, как выполнять вставки. Возможно, есть способ адаптировать предка уровня к динамическому сценарию и получить лучшее время выполнения, чем я описал здесь, но я не уверен, как это сделать.
Сопоставьте свои узлы в HashMap<node-id, node>
,
Теперь, когда вам нужно
определить, является ли какой-либо данный узел дочерним по отношению к возможному родительскому узлу,
Вы можете найти точное местоположение этого узла в дереве из HashMap, а затем переместиться обратно вверх по дереву, используя родительские указатели, чтобы увидеть, лежит ли возможный родительский путь на пути к корню.
В достаточно сбалансированном дереве это будет O(Log n) времени выполнения (для обхода дерева) и O (n) пространства (из HashMap).
Если вы идете с вашим текущим планом хранения пути от каждого узла к корню, то у вас будет O(Log n) времени выполнения (при условии сбалансированного дерева) и O(n * Log n) пространства для хранения длины Log n путь для каждого из n узлов.
Массивы хранят все свои значения в памяти. Если вы хотите сохранить эту собственность, вы должны их использовать. Или, если у вас все в порядке с переходом по нескольким ячейкам памяти, вы можете хранить в каждом узле только его непосредственного родителя и отслеживать до необходимого уровня для выполнения требуемой проверки.