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


посмотри тупо набор модель очень эффективно выбирать, но слишком медленно обновлять