Как найти максимальное расстояние между узлами на дереве?
У меня есть набор из N узлов (не бинарного) дерева. Я хочу найти максимальное расстояние между любыми двумя узлами. (Я определяю расстояние между двумя узлами как сумму расстояний между этими узлами и их наименьшим общим предком).
Я могу легко решить эту проблему в O (n^2), просто вычисляя расстояние между каждым узлом друг к другу и получая максимум, однако я надеюсь на что-то лучшее, поскольку это слишком медленно* для моего приложения сценарий.
(дополнительная информация: в моем сценарии приложения эти узлы на самом деле являются файлами, а дерево-структурой каталогов. Поэтому дерево довольно мелкое (глубина в размер. Фактически, я пытаюсь выяснить, насколько широко распространены мои файлы в каждом наборе.)*
Edit: возможно, я могу задать эквивалентный вопрос, чтобы предложить больше ответов: рассмотрим подмножество исходного дерева, содержащее только узлы в моем наборе и узлы, необходимые для их подключения. Тогда возникает вопрос:--9-->как найти самый длинный простой путь в неориентированном ациклическом графе?
*Edit 2: как указал дидьерк, я на самом деле должен рассматривать наборы папок, а не файлов. Это делает мои наборы меньше, и исчерпывающий подход может быть достаточно быстрым. Тем не менее, было бы полезно увидеть более быстрое решение, и мне любопытно посмотреть, есть ли он.
5 ответов
ваша проблема также известна как поиск диаметра дерева: среди всех кратчайших путей между парами узлов вы ищете самый длинный.
обозначим диаметр дерева S Через d(S), а его высоту-через h (S).
два самых удаленных узла в дереве S с поддеревьями S1...Sd могут находиться либо под одним из его поддеревьев, либо они могут охватывать два поддерева. В первом случае, когда два самых удаленных узла находятся под поддеревом Si, d(S) - это просто d(Si). Во втором случае, когда два наиболее удаленных узла охватывают два поддерева, скажем Si и Sj, их расстояние составляет h(Si) + h(Sj) + 2, потому что два узла должны быть двумя самыми глубокими узлами в каждом поддереве, плюс еще два ребра для соединения двух поддеревьев. Фактически, в этом втором случае Si и Sj должны быть самым высоким и вторым самым высоким поддеревом S.
алгоритм O(n) будет действовать следующим образом
алгоритм d (S)
1. recursively compute d(S1)...d(Sd) and h(S1)...h(Sd) of the subtrees of S.
2. denote by Si be the deepest subtree and Sj the second deepest subtree
3. return max(d(S1), ..., d(Sd), h(Si)+h(Sj)+2)
анализ
строки 2 и 3 каждый принимают O (d) время к вычислять. Но каждый узел рассматривается только один раз этими линиями, поэтому в рекурсии это занимает всего O(n).
предположим, что путь максимальной длины между двумя узлами проходит через наш корневой узел. Затем один из двух узлов должен принадлежать поддереву одного ребенка, а другой-поддереву другого ребенка. Тогда легко видеть, что эти два узла являются самыми низкими / самыми глубокими потомками этих двух детей, что означает, что расстояние между этими двумя узлами height(child1) + height(child2) + 2
. Таким образом, путь максимальной длины между двумя узлами, проходящий через наш корень, равен max-height-of-a-child + second-to-max-height-of-a-child + 2
.
это дает нам простой алгоритм O(n) для поиска общего пути максимальной длины: просто сделайте это для каждого не-листового узла. Поскольку каждый путь должен быть укоренен в некотором не-листовом узле, это гарантирует, что мы рассмотрим правильный путь в какой-то момент.
Поиск высоты поддерева равен O (n), но, поскольку вы можете рекурсивно создавать высоты, найти высоту каждый поддерево удобно также O (n). На самом деле, вам даже не нужно найти высоту как отдельный шаг, вы могли бы найдите путь максимальной длины и высоты поддерева одновременно, то есть этот алгоритм требует только O(n) времени и o(высота дерева) пространства.
Я собираюсь набросать алгоритм, который обходит дерево и рекурсивно вычисляет двое самых дальних детей что это в вашей подгруппе, для каждого узла.
пусть s-ваше подмножество узлов. Для каждого узла мы вводим две переменные, хранящие длину самого длинного и второго по длине пути к ребенку в S. longest
и secondlongest
инициализируются с 0.
алгоритм (начало в корневом узле):
for each child node // we'll skip this if we are a leaf
do recursive call
update longest and secondlongest // use return value of child call
if longest >= 1 // there is a node of S below us
return longest + 1 // increment length
if node in S // we found the first node of S on this path
return 1
return 0 // there is now node of S below us
теперь, каждый узел знает самое длинное и второе самое длинное расстояние до ребенка в S. (Они могут быть равны). Пересечь дерево еще раз и получить максимальную сумму longest
и secondlongest
.
весь алгоритм в O (n). Вы также можете получить максимальную сумму в основном алгоритме, чтобы избежать второго обхода.
У меня есть простой o(n) жадный алгоритм для этой интересной проблемы.
алгоритм
- выберите произвольную вершину X в качестве корня дерева, а затем найдите вершина Y, которая имеет максимальное расстояние с корнем X. сложность этого шага равна O (n).
- сделайте вершину Y как новый корень дерева, затем найдите вершину Z кто имеет максимальное расстояние с корнем Y. А расстояние между Y и Z является максимальным из расстояний в дерево. Сложность этого шага также O (n).
- общая сложность этого жадного алгоритма равна O (n).
доказательство
- очевидно, что Y и Z образуют один диаметр дерева, и мы назовем Y и Z парой углов дерева.
- Теорема: для каждой вершины P в дереве либо Y, либо Z будет вершины, который имеет максимальное расстояние с ним.
- Шаг 1 алгоритма основан на Теорема, так что мы можем легко получить один угол (Y) дерева.
- второй угол Z находится на основе Теорема как хорошо.
расширения
по словам Теорема в доказательстве мы можем решить еще одну более сложную проблему: для каждой вершины дерева вычислите, кто является fareast vertice к нему.
- мы можем найти два угла дерева в o (n) сложности, а затем мы мочь используйте Теорема снова.
- из угла Y и Z делаем dfs соответственно, и для каждой вершины p[i] мы можем получить расстояние до Y и Z(мы называем их disY[i] и disZ[i]), поэтому расстояние фариста для p[i] равно max(disY[i], disZ[i]). Из-за того, что мы просто делаем dfs дважды, поэтому мы можем получить информацию в O(n) сложность.
- эта расширенная проблема может решена с помощью дерева complexible динамические Программирование, сложность которого также O (n).
это рекурсивный алгоритм. Вот псевдо-код (непроверенный код ocaml):
type result = {n1 : node; n2 : node; d1 : int (* depth of node n1 *); d2 : int; distance: int}
(* a struct containing:
- the couple of nodes (n1,n2),
- the depth of the nodes, with depth(n1) >= depth(n2)
- the distance between n1 & n2 *)
let find_max (n : node) : result =
let max (s1 : result) (s2 : result) = if s1.distance < s2.distance then s2 else s1 in
let cl : node list = Node.children n in
if cl = []
then { n1 = n; n2 = n; d1 = 0; d2 = 0; distance = 0 }
else
let ml = List.map find_max cl in
let nl = List.map (fun e -> e.n1, e.d1+1) ml in
let (k1,d1)::(k2,d2)::nl = nl in
let k1,d1,k2,d2 = if d1 > d2 then k1,d1,k2,d2 else k2,d2,k1,d1 in
let s = {n1 = k1;n2 = k2; d1 = d1; d2 = d2; distance = d1+d2} in
let m1 = List.fold_left (fun r (e,d) ->
if r.d1< d
then { r with n1 = e; d1 = d; distance = d+d2 }
else if r.d2 < d
then { r with n2 = e; d2 = d; distance = d+d1 }
else r) s nl in
max m1 (List.fold_left max (List.hd ml) (List.tl ml))
на m1
значение строится путем сохранения двух самых глубоких узлов списка nl с расстоянием, являющимся суммой их глубин.
List.map
- это функция, применяя заданную функцию ко всем элементам списка и возвращает список результатов.
List.fold_left
- это функция, рекурсивно применяющая данную функцию к аккумулятору и элементам списка, каждый раз использование результата предыдущего приложения в качестве нового значения аккумулятора. Результатом является последнее значение аккумулятора.
List.hd
возвращает первый элемент списка.
List.tl
возвращает список без первого элемента.