Двоичное дерево здесь не обязательно может быть двоичным деревом поиска.
Структура может быть принята как -
struct node {
int data;
struct node *left;
struct node *right;
};
Максимальное решение, которое я мог бы разработать с другом, было что-то в этом роде.
Рассмотрим это двоичное дерево :
Выход по обходу по порядку - 8, 4, 9, 2, 5, 1, 6, 3, 7
А доходность прохождения заказа - 8, 9, 4, 5, 2, 6, 7, 3, 1
Так, например, если мы хотим найти общего предка узлов 8 и 5, то мы составим список всех узлов, которые находятся между 8 и 5 в обходе дерева порядков, которое в этом случае оказывается [4, 9 , 2]. Затем мы проверяем, какой узел в этом списке появляется последним в обходе после заказа, а это 2. Следовательно, общий предок для 8 и 5 равен 2.
Я полагаю, что сложность этого алгоритма - O (n) (O (n) для обходов по порядку / порядку, остальные шаги снова являются O (n), поскольку они являются не более чем простыми итерациями в массивах). Но есть большая вероятность, что это неправильно. :-)
Но это очень грубый подход, и я не уверен, что он сломается в каком-то случае. Есть ли другое (возможно, более оптимальное) решение этой проблемы?
Ответы:
Ник Джонсон прав, что алгоритм временной сложности O (n) - это лучшее, что вы можете сделать, если у вас нет родительских указателей.) Для простой рекурсивной версии этого алгоритма см. Код в посте Киндинга, который выполняется за время O (n). ,
Но имейте в виду, что если у ваших узлов есть родительские указатели, возможен улучшенный алгоритм. Для обоих рассматриваемых узлов создайте список, содержащий путь от корня до узла, начиная с узла и вставляя спереди родительский элемент.
Таким образом, для 8 в вашем примере вы получаете (показывая шаги): {4}, {2, 4}, {1, 2, 4}
Сделайте то же самое для вашего другого рассматриваемого узла, в результате чего (шаги не показаны): {1, 2}
Теперь сравните два списка, которые вы создали, ища первый элемент, в котором список отличается, или последний элемент одного из списков, в зависимости от того, что произойдет раньше.
Этот алгоритм требует времени O (h), где h - высота дерева. В худшем случае O (h) эквивалентно O (n), но если дерево сбалансировано, то это только O (log (n)). Это также требует O (h) место. Возможна улучшенная версия, которая использует только постоянное пространство, с кодом, показанным в посте CEGRD
Независимо от того, как построено дерево, если это будет операция, которую вы выполняете над деревом много раз без изменения между ними, есть другие алгоритмы, которые вы можете использовать, которые требуют подготовки O (n) [linear] time, но затем находят любой пара занимает только O (1) [постоянное] время. Ссылки на эти алгоритмы см. На самой низкой странице общих предков в Википедии . (Благодарим Джейсона за оригинальную публикацию этой ссылки)
источник
O(h)
- толькоO(log(n))
если дерево сбалансировано. Для любого дерева, будь то двоичное или нет, если у вас есть родительские указатели, вы можете определить путь от листа к корню воO(h)
времени, просто следуя указателю родителя доh
времени. Это дает вам путь от листа до корня. Если пути хранятся в виде стека, то итерирование стека дает вам путь от корня до листа. Если вам не хватает родительских указателей и у вас нет специальной структуры для дерева, то поиск пути от корня к листу займет некотороеO(n)
время.Начиная с
root
узла и двигаясь вниз, если вы обнаружите какой-либо узел, который имеет одногоp
илиq
как его прямого потомка, то это LCA. (edit - это должно быть, еслиp
илиq
является значением узла, вернуть его. В противном случае произойдет сбой, когда один изp
илиq
является прямым потомком другого.)Иначе, если вы найдете узел с
p
его правым (или левым) поддеревом иq
его левым (или правым) поддеревом, то это LCA.Исправленный код выглядит так:
Приведенный ниже код завершается ошибкой, когда один из них является прямым потомком другого.
Код в действии
источник
Вот рабочий код в JAVA
источник
Ответы, данные до сих пор, используют рекурсию или хранят, например, путь в памяти.
Оба этих подхода могут потерпеть неудачу, если у вас очень глубокое дерево.
Вот мой взгляд на этот вопрос. Когда мы проверяем глубину (расстояние от корня) обоих узлов, если они равны, то мы можем безопасно двигаться вверх от обоих узлов к общему предку. Если одна из глубин больше, то мы должны двигаться вверх от более глубокого узла, оставаясь в другом.
Вот код:
Временная сложность этого алгоритма: O (n). Пространственная сложность этого алгоритма: O (1).
Что касается вычисления глубины, мы можем сначала вспомнить определение: если v - корень, глубина (v) = 0; В противном случае глубина (v) = глубина (родитель (v)) + 1. Мы можем вычислить глубину следующим образом:
источник
Ну, этот вид зависит от того, как структурировано ваше двоичное дерево. Предположительно, у вас есть какой-то способ найти нужный листовой узел по корню дерева - просто примените его к обоим значениям, пока выбранные вами ветви не расходятся.
Если у вас нет способа найти нужный лист по заданному корню, тогда ваше единственное решение - как в обычной работе, так и для поиска последнего общего узла - это поиск дерева методом грубой силы.
источник
Это можно найти по адресу: - http://goursaha.freeoda.com/DataStructure/LowestCommonAncestor.html
источник
Автономный алгоритм наименее общих предков Тарьяна достаточно хорош (см. Также Википедию ). Есть больше о проблеме (самой низкой общей проблеме предка) в Википедии .
источник
Чтобы узнать общего предка двух узлов:
Это будет работать для бинарного дерева поиска.
источник
Я сделал попытку с иллюстративными картинками и рабочим кодом на Java,
http://tech.bragboy.com/2010/02/least-common-ancestor-without-using.html
источник
Приведенный ниже рекурсивный алгоритм будет работать в O (log N) для сбалансированного двоичного дерева. Если какой-либо из узлов, переданных в функцию getLCA (), совпадает с корневым, то корневым будет LCA, и не будет необходимости выполнять какую-либо проверку.
Тестовые случаи. [1] Оба узла n1 и n2 находятся в дереве и находятся по обе стороны от их родительского узла. [2] Либо узел n1, либо n2 является корнем, а LCA - корнем. [3] В дереве есть только n1 или n2, LCA будет либо корневым узлом левого поддерева корня дерева, либо LCA будет корневым узлом правого поддерева корня дерева.
[4] Ни n1, ни n2 нет в дереве, здесь нет LCA. [5] Оба n1 и n2 находятся на одной прямой рядом друг с другом, LCA будет либо n1, либо n2, который всегда находится близко к корню дерева.
источник
Просто идите вниз от всего дерева,
root
пока оба заданных узла, скажем,p
иq
, для которых должен быть найден Ancestor, находятся в одном поддереве (то есть их значения меньше или оба больше, чем у корня).Это идет прямо от корня до Наименьшего Общего Предка, не смотря на остальную часть дерева, так что это в значительной степени так же быстро, как и доходит. Несколько способов сделать это.
в случае переполнения я бы сделал (root.val - (long) p.val) * (root.val - (long) q.val)
источник
источник
Рассмотрим это дерево
Если мы проведем обход по порядку и по предзаказу и найдем первого общего предшественника и преемника, мы получим общего предка.
предварительный заказ => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,12,7 предварительный заказ => 7,3,1,0,2,6,4 , 5,12,9,8,11,10,13,15,14
Наименее общий предок 8,11
в предзаказе имеем => 9,14,15,13,12,7 после 8 и 11 в предзаказе имеем => 7,3,1,0,2,6,4,5,12,9 до 8 и 11
9 - это первое общее число, которое встречается после 8 и 11 в предварительном заказе и до 8 и 11 в предварительном заказе, следовательно, 9 является ответом
Наименее общий предок 5,10
11,9,14,15,13,12,7 в почтовом заказе 7,3,1,0,2,6,4 в предварительном заказе
7 является первым числом, которое встречается после 5,10 в предварительном заказе и до 5,10 в предварительном заказе, следовательно, 7 является ответом
источник
Если это полное двоичное дерево с дочерними узлами x, равными 2 * x и 2 * x + 1, то есть более быстрый способ сделать это
Как это работает
Это работает, потому что в основном рекурсивно делит большее число на два, пока оба числа не станут равными. Это число является общим предком. Разделение фактически является операцией правильного сдвига. Поэтому нам нужно найти общий префикс из двух чисел, чтобы найти ближайшего предка
источник
В Scala вы можете:
источник
источник
Вот способ C ++ сделать это. Постарались сделать алгоритм максимально простым для понимания:
Как это использовать:
источник
Самый простой способ найти самого низкого общего предка - использовать следующий алгоритм:
источник
Я нашел решение
В зависимости от 3 обходов, вы можете решить, кто является LCA. От LCA найдите расстояние обоих узлов. Добавьте эти два расстояния, что является ответом.
источник
Вот что я думаю,
Сложность: шаг 1: O (n), шаг 2 = ~ O (n), всего = ~ O (n).
источник
Вот два подхода в c # (.net) (оба обсуждались выше) для справки:
Рекурсивная версия поиска LCA в двоичном дереве (O (N) - так как самое большее посещается каждый узел) (основные точки решения - это то, что LCA - это (a) единственный узел в двоичном дереве, где оба элемента находятся по обе стороны от поддеревьев (слева) и справа) это LCA. (b) И также не имеет значения, какой узел присутствует с обеих сторон - изначально я пытался сохранить эту информацию, и, очевидно, рекурсивная функция стала настолько запутанной. Как только я понял это, она стала очень элегантной.
Поиск по обоим узлам (O (N)) и отслеживание путей (использует дополнительное пространство - поэтому, # 1, вероятно, лучше, даже если пространство, вероятно, незначительно, если двоичное дерево хорошо сбалансировано, так как тогда дополнительное потребление памяти будет только в O (журнал (N)).
так что пути сравниваются (по сути, аналогично принятому ответу - но пути рассчитываются, если предположить, что указательный узел отсутствует в узле двоичного дерева)
Только для завершения ( не относится к вопросу ), LCA в BST (O (log (N))
тесты
рекурсивные:
где выше частная рекурсивная версия вызывается следующим публичным методом:
Решение путем отслеживания путей обоих узлов:
где FindNodeAndPath определяется как
BST (LCA) - не связано (только для завершения для справки)
Модульные тесты
источник
Если кто-то заинтересован в псевдокоде (для университетских домашних работ), вот один.
источник
Хотя на этот вопрос уже был дан ответ, это мой подход к этой проблеме с использованием языка программирования C. Хотя код показывает двоичное дерево поиска (что касается insert ()), алгоритм работает и для двоичного дерева. Идея состоит в том, чтобы обойти все узлы, которые лежат от узла A к узлу B при обходе по порядку, и искать их индексы в обходе по порядку. Узел с максимальным индексом в прохождении после заказа является самым низким общим предком.
Это рабочий код C для реализации функции по нахождению наименьшего общего предка в двоичном дереве. Я также предоставляю все функции утилит и т. Д., Но для быстрого понимания перейдем к CommonAncestor ().
источник
Может быть еще один подход. Однако это не так эффективно, как тот, который уже предлагался в ответах.
Создайте вектор пути для узла n1.
Создайте второй вектор пути для узла n2.
Вектор пути, подразумевающий множество узлов из этого, будет проходить до нужного узла.
Сравните оба вектора пути. Индекс, где они не совпадают, возвращает узел с этим индексом - 1. Это даст LCA.
Минусы для этого подхода:
Необходимо пересечь дерево дважды для расчета векторов пути. Нужно дополнительное пространство O (h) для хранения векторов пути.
Однако это легко реализовать и понять.
Код для расчета вектора пути:
источник
Попробуй вот так
источник
Грубый путь:
Проблема описанного выше метода заключается в том, что мы будем выполнять поиск несколько раз, то есть существует вероятность, что каждый узел будет проходить несколько раз. Мы можем преодолеть эту проблему, если сможем записать информацию, чтобы не обрабатывать ее снова (подумайте о динамическом программировании).
Поэтому вместо того, чтобы находить каждый узел, мы ведем запись того, что уже найдено.
Лучший способ:
Код:
источник
Код для поиска в ширину, чтобы убедиться, что оба узла находятся в дереве. Только тогда двигайтесь вперед с поиском LCA. Пожалуйста, прокомментируйте, если у вас есть предложения по улучшению. Я думаю, что мы, вероятно, можем пометить их как посещенных и возобновить поиск в определенной точке, где мы остановились, чтобы улучшить для второго узла (если он не найден, ПОСЕТИЛ)
источник
Вы правы, что без родительского узла решение с обходом даст вам O (n) сложность по времени.
Обходной подход путь Предположим, что вы находите LCA для узлов A и B, самый простой подход заключается в том, чтобы сначала получить путь от корня к A, а затем получить путь от корня к B. После того, как вы получите эти два пути, вы можете легко перебрать их и найдите последний общий узел, который является самым низким общим предком A и B.
Рекурсивное решение Другой подход заключается в использовании рекурсии. Во-первых, мы можем получить LCA как из левого, так и из правого дерева (если оно существует). Если любой из A или B является корневым узлом, тогда корень - это LCA, и мы просто возвращаем корень, который является конечной точкой рекурсии. Поскольку мы продолжаем делить дерево на поддеревья, в конечном итоге мы попадем либо в А, либо в В.
Чтобы объединить решения подзадач, если LCA (левое дерево) возвращает узел, мы знаем, что и A, и B находятся в левом дереве, и возвращаемый узел является конечным результатом. Если LCA (слева) и LCA (справа) возвращают непустые узлы, это означает, что A и B находятся в левом и правом дереве соответственно. В этом случае корневой узел является самым низким общим узлом.
Проверьте Lowest Common Ancestor для детального анализа и решения.
источник
Некоторые решения здесь предполагают, что есть ссылка на корневой узел, некоторые предполагают, что дерево является BST. Совместное использование моего решения с использованием hashmap без ссылки на
root
узел и дерево может быть BST или не BST:источник
Решение 1: Рекурсивно - Быстрее
Решение 2: Итеративное - Использование родительских указателей - Медленнее
источник