O (1) алгоритм определения, является ли узел потомком другого узла в многоходовом дереве?
представьте себе следующее дерево:
A
/
B C
/
D E F
Я ищу способ запроса, если, например, F является потомком A (Примечание: F не должен быть прямые потомок F), что в данном конкретном случае было бы верно. Только ограниченное количество потенциальных родительских узлов необходимо протестировать против большего пула потенциальных потомков.
UPDATE: при тестировании, является ли узел потомком узла в потенциальном Родительском пуле, он должен быть протестировано против всех потенциальных родительских узлов.
вот что придумал:
-
преобразовать многоходовое дерево в trie, т. е. назначить следующие префиксы каждому узлу в вышеуказанном дереве:
A = 1 B = 11 C = 12 D = 111 E = 112 F = 121
-
затем зарезервируйте битовый массив для каждого возможного размера префикса и добавьте родительские узлы для тестирования, т. е. если C добавлен в пул потенциальных родительских узлов, сделайте:
1 2 3 <- Prefix length *[1] [1] ... [2] *[2] ... [3] [3] ... [4] [4] ... ... ...
-
при проверке если узел является потомком потенциального родительского узла, возьмите его префикс trie, найдите первый символ в первом " массиве префиксов "(см. выше), и если он присутствует, найдите второй символ префикса во втором" массиве префиксов " и так далее, т. е. тестирование F приводит к:
F = 1 2 1 *[1] [1] ... [2] *[2] ... [3] [3] ... [4] [4] ... ... ...
так что да F, является потомком C.
этот тест кажется наихудшим случаем O( n), где n = максимальная длина префикса = максимальная глубина дерева, поэтому его наихудший случай точно равен очевидному способ просто подняться по дереву и сравнить узлы. Однако это работает намного лучше, если тестируемый узел находится в нижней части дерева, а потенциальный родительский узел находится где-то вверху. Объединение обоих алгоритмов смягчит оба наихудших сценария. Однако, память, забота.
есть ли другой способ для этого? Любые указатели очень ценятся!
7 ответов
ваши входные деревья всегда статичны? Если это так, то вы можете использовать алгоритм наименьшего общего предка для ответа на вопрос потомка is в O(1) времени с построением o(n) времени/пространства. Запрос LCA получает два узла и спрашивает, какой из них является самым низким узлом в дереве, поддерево которого содержит оба узла. Затем вы можете ответить на запрос IsDescendent одним запросом LCA, если LCA(A, B) == A или LCA (A, B) == B, то один является потомком другого.
этой Topcoder tuorial алгоритм дает подробное обсуждение проблемы и несколько решений на различных уровнях сложности/эффективности кода.
Я не знаю, будет ли это соответствовать вашей проблеме, но один из способов хранения иерархий в базах данных с быстрыми функциями "дайте мне все от этого узла и вниз" - это сохранить "путь".
например, для дерева, которое выглядит так:
+-- b
|
a --+ +-- d
| |
+-- c --+
|
+-- e
вы бы сохранили строки следующим образом, предполагая, что буква в приведенном выше дереве является " id " каждой строки:
id path
a a
b a*b
c a*c
d a*c*d
e a*c*e
чтобы найти всех потомков определенного узла, вы бы сделали запрос "STARTSWITH" на столбец "путь", т. е.. все узлы с путем, который начинается с a*c*
чтобы узнать, является ли конкретный узел потомком другого узла, вы увидите, начался ли самый длинный путь с самого короткого пути.
например:
- e является потомком a, так как
a*c*e
начинается сa
- d является потомком c, так как
a*c*d
начинается сa*c
это было бы полезно в вашем случае?
для обхода любого дерева потребуются шаги "глубина дерева". Поэтому, если вы поддерживаете сбалансированную древовидную структуру, доказуемо, что вам понадобится O (log n) операции для поиск операции. Из того, что я понимаю, ваше дерево выглядит особенным, и вы не можете поддерживать его сбалансированным образом, не так ли? Так что O (n) будет возможно. Но это плохо во время создания дерева в любом случае, поэтому вы, вероятно, умрете, прежде чем использовать поиск в любом случае...
в зависимости от того, как часто вам нужно будет это поиск операции по сравнению с вставить, вы можете решить заплатить во время вставить для поддержания дополнительной структуры данных. Я бы предложил хеширование, если вы действительно нужно амортизированной O (1). На каждую операцию вставки вы ставите все родители узел в хэш-таблицу. По вашему описанию это может быть O (n) элементы на данном вставить. Если вы это сделаете n вставка это звучит плохо (к O (n^2)), но на самом деле дерево не может ухудшить что плохо, поэтому вы, вероятно, получите амортизированный общий размер hastable O (N log n). (на самом деле, log n часть зависит от степени деграции вашего дерева. Если вы ожидаете, что это будет максимальная degraed, не делай этого.)
Итак, вы заплатите около O (log n) на каждый вставить, и получить эффективность hashtable O (1) на поиск.
для дерева M-way вместо вашего битового массива, почему бы просто не сохранить двоичный "trie id"(используя M бит на уровень) С каждого узла? Пример (предполагая, что M==2) : A=0b01, B=0b0101, C=0b1001, ...
затем вы можете выполнить тест в O (1):
bool IsParent(node* child, node* parent)
{
return ((child->id & parent->id) == parent->id)
}
вы можете сжать хранилище до ceil(lg2 (M)) бит на уровень, если у вас есть fast FindMSB () функция, которая возвращает позицию самого значительного бита:
mask = (1<<( FindMSB(parent->id)+1) ) -1;
retunr (child->id&mask == parent->id);
в обходе предварительного порядка каждый набор потомков смежен. Например,
A B D E C F
+---------+ A
+---+ B
+ D
+ E
+-+ C
+ F
Если вы можете предварительно обработать, то все, что вам нужно сделать, это пронумеровать каждый узел и вычислить интервал потомка.
Если вы не можете выполнить предварительную обработку, то a ссылка/вырезать дерево предлагает производительность O (log n) для обновлений и запросов.
вы можете ответить на запрос формы " является ли узел A потомком узла B?- в постоянном времени, используя только две вспомогательные системы.
предварительно обработайте дерево, посетив в глубине-первый порядок, и для каждого узла сохраните его начальное и конечное время в посещении в двух массивах Start[] и End[].
Итак, предположим, что End[u] и Start[u] - это соответственно время окончания и начала посещения узла u.
тогда узел u является потомком узла v, Если и только если:
Start[v]
и вы сделали, проверка этого условия требует только два поиска в массивах Start и End
посмотри тупо набор модель очень эффективно выбирать, но слишком медленно обновлять