Постепенно сохраняйте путь от корневого узла к узлу многостороннего дерева во время вставки, чтобы операция хранения не имела сложности O (n)

Я хотел бы спросить, знает ли кто-нибудь эффективный способ сохранить путь от корневого узла к новому узлу многостороннего дерева во время вставки нового узла. Например, если у меня есть следующее дерево:

Многостороннее дерево

Для каждого узла я в настоящее время сохраняю массив пути к узлу от корневого узла во время вставки следующим образом, назначая уникальный int ID каждому дочернему элементу на той же глубине:

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.

Эта операция поиска будет быстрой, проблема заключается в создании массива путей по мере того, как дерево углубляется.

Мы ценим любые предложения.

Спасибо за внимание.


person tonix    schedule 25.08.2019    source источник
comment
Как часто вы вставляете и запрашиваете? А как насчет удаления?   -  person Yola    schedule 25.08.2019
comment
Поиски могут выполняться чаще, чем вставки, но для одной ветви может происходить много вставок, что приводит к глубокому дереву. Также возможны операции удаления и замены, но это другая история, и я обрабатываю их по-другому.   -  person tonix    schedule 25.08.2019
comment
Правильно ли я вас понял, если ваш запрос более-менее имеет форму: given a node x, is the k'th node on the path from root to x the node y?   -  person SecularisticSloth    schedule 25.08.2019
comment
Прежде чем мы перейдем к более сложным ответам ... по умолчанию каждый дочерний элемент просто хранит указатель на своего родителя, а иногда и на его глубину. Тогда на запросы предков легко ответить, просто пройдя этот список от дочернего до глубины родителя. Что в этом подходе вам не подходит?   -  person Matt Timmermans    schedule 25.08.2019
comment
@SecularisticSloth Не совсем так. Мне нужно знать, given two nodes x and y, x is an ancestor of y. При этом мне нужно сохранить путь от root к x и присвоить y уникальный идентификатор его глубины / уровня в дереве.   -  person tonix    schedule 25.08.2019
comment
@tonix Итак, во втором примере: является ли узел 1 глубины 1 родителем / предком узла 1 глубины 3? вы указываете только глубину узлов, чтобы однозначно их идентифицировать, поскольку одни и те же идентификаторы появляются на нескольких уровнях дерева?   -  person SecularisticSloth    schedule 25.08.2019
comment
@SecularisticSloth Каждый раз, когда я проверяю, является ли узел x предком y, у меня уже есть ссылка на узлы x и y, и я также знаю глубину y и глубину x. Я не знаю, действительно ли x является предком (это может быть даже дочерний элемент y, или, может быть, это узел в другой ветке, как в первом примере). Если с the same identities appear on multiple levels in the tree вы имеете в виду the same values, то да, это может случиться, но для меня это не проблема, меня не интересует значение узла, мне просто нужно знать, сохраняются ли отношения предок-потомок.   -  person tonix    schedule 25.08.2019
comment
@tonix Я добавил ответ, который, как мне кажется, решает вашу проблему. Может быть немного неясно, не стесняйтесь спрашивать.   -  person SecularisticSloth    schedule 25.08.2019


Ответы (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). Однако структура данных статична, поэтому в решении не указано, как выполнять вставки. Возможно, существует способ адаптировать предка уровня к динамическому сценарию и получить даже лучшее время работы, чем я описал здесь, но я не уверен, как это сделать.

person SecularisticSloth    schedule 25.08.2019
comment
Спасибо за ваш ответ! Мне очень нравится эта логарифмическая идея. Постараюсь реализовать и дам знать. Я также пришел к идее, основанной на эвристическом предположении, что мое дерево растет более вертикально, чем горизонтально, т.е. может быть только одна высокая, длинная, глубокая ветвь с небольшим количеством дочерних элементов на уровень / глубину, если таковые имеются. Я обновлю свой вопрос и буду рад услышать ваше мнение! - person tonix; 25.08.2019

Все значения массивов непрерывно хранятся в памяти. Если вы хотите сохранить это свойство, вы должны их использовать. Или, если у вас все в порядке с надеждой на несколько ячеек памяти, вы можете сохранить в каждом узле только его непосредственного родителя и проследить до необходимого уровня, чтобы выполнить требуемую проверку.

person Yola    schedule 25.08.2019
comment
store in every node only its immediate parent and trace up Я думаю, что таким образом поиск будет медленным. Для листа глубоко в дереве (например, с глубиной 100.000) мне пришлось бы подпрыгивать до тех пор, пока его узел не сохранится на глубине 2, если я проверяю, является ли узел на глубине 2 предком или нет. Может быть, есть интересный способ использовать единственный массив, на который указывает каждый узел в дереве? Единственное, что приходит мне в голову: если я создаю 1-й дочерний элемент из другого узла, зачем мне создавать новый массив? Я могу добавить к тому, на который указал родитель ... Но что тогда произойдет, если я разветвлю дочерний элемент 2 от того же родителя? - person tonix; 25.08.2019
comment
@tonix Если вы когда-нибудь найдете такой массив, никому не говорите, просто позвоните мне. Мы будем вести дела вместе;) Что касается вашей второй мысли, я считаю, что некоторые вставки испортят любой трюк, который вы можете придумать, чтобы сократить время поиска, для таких вставок вам нужно будет воссоздавать большую часть, возможно, даже всю структуру. - person Yola; 25.08.2019
comment
@tonix Я советую вам дать людям больше информации о проблеме в другом вопросе. Может быть, лучше подойдет другая структура данных. - person Yola; 25.08.2019
comment
We will do business together: D Звучит интересно, ах, сейчас я просто хочу расширить свои знания, но если я когда-нибудь что-нибудь найду, я обещаю, что напишу комментарий здесь в этой ветке на SO. - person tonix; 25.08.2019
comment
И да, я еще раз подумаю о своей проблеме и, в конце концов, опубликую еще один вопрос, спасибо за вашу помощь! - person tonix; 25.08.2019

Сопоставьте свои узлы в HashMap<node-id, node>.


Теперь, когда тебе нужно

определить, является ли какой-либо данный узел дочерним по отношению к возможному родительскому узлу,

Вы можете найти точное местоположение этого узла в дереве из HashMap, а затем вернуться вверх по дереву, используя родительские указатели, чтобы увидеть, находится ли возможный родительский элемент на пути к корню.

В достаточно сбалансированном дереве это будет время выполнения O (Log n) (для обхода дерева) и O (n) пространства (HashMap).


Если вы пойдете с вашим текущим дизайном хранения пути от каждого узла до корня, тогда у вас будет время выполнения O (Log n) (при условии сбалансированного дерева) и O (n * Log n) пространство для хранения Зарегистрируйте путь длиной n для каждого из n узлов.

person displayName    schedule 25.08.2019
comment
You can find the exact location of that node in the tree from the HashMap вы имеете в виду, что я начинаю прыжки с возможного потомка x возможного предка y и подпрыгиваю вверх, пока не достигну y, используя информацию о глубине y? В чем отличие от подхода, предложенного SecularisticSloth? - person tonix; 25.08.2019
comment
@tonix: я думаю, что мой подход прост и понятен. Подход СС гораздо более запутанный. (Не означает, что это плохо. Возможно, это именно то, что вы ищете.) Во-вторых, после беглого прочтения первого параграфа этого ответа, сложность пространства и времени выполнения моего решения ниже. - person displayName; 25.08.2019
comment
@displaName Первый абзац - время работы решения, изначально предложенного OP. Второй абзац - это время работы предлагаемого мной решения. Я понимаю, что это может ввести в заблуждение. Однако ваше предположение о том, что дерево сбалансировано, неверно, я думаю. Как указано в вопросе OP, дерево растет в высоту, но не намного в ширину. На каждом уровне всего несколько узлов. - person SecularisticSloth; 26.08.2019
comment
Таким образом, время выполнения операции поиска будет O(n), (или O(h)). Однако вставка будет выполняться в O(1), а пробел - в O(n). Если дерево не слишком высокое или не будет выполняться большое количество операций поиска, это, вероятно, путь. - person SecularisticSloth; 26.08.2019
comment
@displayName SecularisticSloth прав, дерево неуравновешено и сильно растет в высоту, согласно моему вопросу As my tree grows a lot in height (the number of children per depth is relatively low, but the depth can be high), ... Если я правильно понял, ваше решение будет подразумевать обратный переход всех узлов от листа к корню, чтобы найти предка в- между. Это, однако, будет O(n) (наихудший случай для дерева, состоящего только из одной ветви без многосторонних ветвей) или O(h), где h - высота дерева. Я обновлю свой вопрос как можно скорее, когда у меня будет время! Всем спасибо! - person tonix; 26.08.2019