Наименьший Общий Алгоритм Предка

поэтому я рассматривал реализацию алгоритма наименьшего общего предка. Я просмотрел множество различных алгоритмов (в основном вариации решения Траяна или вариации RMQ).

Я использую недвоичное дерево. Мое дерево часто меняется между запросами, и поэтому предварительная обработка не обязательно будет полезной. Дерево не должно иметь более 50-75 узлов. Мне интересно, Стоит ли мне использовать их алгоритмы или просто придерживаться своих собственных.

Мой Алгоритм

myLCA(node1, node2) {
    parentNode := [ ]
    while (node1!=NULL) {
         parentNode.push(node1)
         node1 := node1.parent
    }
     while (node2!=NULL) {
         for i in parentNode.size {
             if (parentNode(i) == node2) {
                 return node2; 
             }
         }
         node2 := node2.parent
     }

}       

6 ответов


как уже упоминалось, ваш алгоритм в настоящее время квадратичной. Это не может быть проблемой для набора данных размером 50-75 узлов, но в любом случае его легко изменить на линейное время без использования каких-либо наборов или хэш-таблиц, просто записав полный путь к корню для каждого узла, а затем вернувшись от корня и ища первый другой узел. Предыдущий узел (который является общим родителем этих 2-х различных узлах) тогда LCA:

linearLCA(node1, node2) {
    parentNode1 := [ ]
    while (node1!=NULL) {
         parentNode1.push(node1)
         node1 := node1.parent
    }
    parentNode2 := [ ]
    while (node2!=NULL) {
         parentNode2.push(node2)
         node2 := node2.parent
    }
    while (node1 == node2 && !isEmpty(parentNode1) && !isEmpty(parentNode2)) {
        oldNode := node1
        node1 := parentNode1.pop()
        node2 := parentNode2.pop()
    }
    if (node1 == node2) return node1    // One node is descended from the other
    else return oldNode                 // Neither is descended from the other
}

изменить 27/5/2012: обрабатывать случай, в котором один узел происходит от другого, что в противном случае приведет к попытке pop() пустой стек. Спасибо проклятому, что указал на это. (Я также понял, что достаточно отслеживать один oldNode.)


для такого маленького дерева я бы не стал реализовывать ничего более сложного. Ваше решение выглядит хорошо, хотя временная сложность в квадрате с точки зрения высоты дерева. Если вы можете легко реализовать Set (большинство языков имеют встроенный), то алгоритм может быть изменен на,

  1. пройдите от первого узла до корня и соберите все узлы в набор
  2. пройдите от второго узла до корня и проверьте, является ли текущий узел существует в этом наборе. Если да, то это общий предок.

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

A
|
B
|
C

при попытке найти наименьший общий предок B и C этот алгоритм сообщит B, который может быть или не быть истинным в зависимости от того, как вы определяете предка.


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

сколько раз этот алгоритм будет выполняться при нормальной (или напряженной) работе вашего приложения? Это заставит пользователя ждать дольше, чем необходимо? Являются ли другие алгоритмы на другой порядок быстрее, чем ваши? (Кто-то знакомый с помощью алгоритмов можно дать более подробный ответ на этот вопрос.)

Я думаю, что не стоит оптимизировать немного кода, если вы не увидите значительных результатов (некоторые люди считают, что преждевременная оптамизация корень всех зол)


ваш алгоритм квадратичен, но его можно легко сделать линейным.

просто используйте hashtable (т. е. set) для parentNode, вместо списка. Таким образом, проверка, находится ли узел в parentNode будет O(1) вместо O(n).


Я как раз писал в блоге о том, как я должен реализовать свой собственный алгоритм для этой проблемы, но продлен до набора узлов с произвольной длины. Вы можете найти его здесь (с пошаговым графическим объяснением того, как он работает)

http://bio4j.com/blog/2012/02/finding-the-lowest-common-ancestor-of-a-set-of-ncbi-taxonomy-nodes-with-bio4j/

спасибо,

Пабло


У меня есть одно упрощенное решение сортировка двух элементов и самый низкий слева и самый высокий справа посетить корень Def recurse (root) верните nil, если root.пусто? если слева = root вернуть корень elsif left

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