Drzewo binarne tutaj niekoniecznie musi być drzewem wyszukiwania binarnego.
Strukturę można przyjąć jako -
struct node {
int data;
struct node *left;
struct node *right;
};
Maksymalnym rozwiązaniem, które mogłem wymyślić z przyjacielem, było coś takiego -
rozważ to drzewo binarne :
Przechodzenie wewnętrzne daje - 8, 4, 9, 2, 5, 1, 6, 3, 7
A przechodzenie po zamówieniu daje - 8, 9, 4, 5, 2, 6, 7, 3, 1
Na przykład, jeśli chcemy znaleźć wspólnego przodka węzłów 8 i 5, tworzymy listę wszystkich węzłów, które znajdują się między 8 a 5 w przechodzeniu drzewa wewnętrznego, którym w tym przypadku jest [4, 9 , 2]. Następnie sprawdzamy, który węzeł na tej liście pojawia się jako ostatni w przemierzaniu zamówienia pocztowego, czyli 2. Stąd wspólnym przodkiem dla 8 i 5 jest 2.
Złożoność tego algorytmu, jak sądzę, wynosi O (n) (O (n) dla przechodzenia w kolejności / po kolejności, pozostałe kroki to znowu O (n), ponieważ są one niczym innym jak prostymi iteracjami w tablicach). Ale jest duża szansa, że to źle. :-)
Ale jest to bardzo surowe podejście i nie jestem pewien, czy w jakimś przypadku się zepsuje. Czy istnieje inne (być może bardziej optymalne) rozwiązanie tego problemu?
Odpowiedzi:
Nick Johnson ma rację, że algorytm złożoności czasowej O (n) jest najlepszym, co możesz zrobić, jeśli nie masz wskaźników nadrzędnych.) Aby uzyskać prostą rekurencyjną wersję tego algorytmu, zobacz kod w poście Kindinga, który działa w czasie O (n) .
Pamiętaj jednak, że jeśli węzły mają wskaźniki nadrzędne, możliwy jest ulepszony algorytm. Dla obu tych węzłów utwórz listę zawierającą ścieżkę od korzenia do węzła, zaczynając od węzła i wstawiając z przodu rodzica.
Zatem dla 8 w Twoim przykładzie otrzymujesz (pokazując kroki): {4}, {2, 4}, {1, 2, 4}
Zrób to samo dla drugiego węzła, którego dotyczy problem, co spowoduje (kroki niepokazane): {1, 2}
Teraz porównaj dwie listy, które zrobiłeś, szukając pierwszego elementu, w którym lista się różni, lub ostatniego elementu jednej z list, w zależności od tego, co nastąpi wcześniej.
Algorytm ten wymaga czasu O (h), gdzie h jest wysokością drzewa. W najgorszym przypadku O (h) jest równoważne O (n), ale jeśli drzewo jest zrównoważone, to jest tylko O (log (n)). Wymaga również spacji O (h). Możliwa jest ulepszona wersja, która używa tylko stałej spacji, z kodem pokazanym w poście CEGRD
Bez względu na to, jak drzewo jest zbudowane, jeśli będzie to operacja, którą wykonasz wiele razy na drzewie bez zmiany jej w międzyczasie, istnieją inne algorytmy, których możesz użyć, które wymagają przygotowania czasu O (n) [liniowe], ale potem znalezienie para zajmuje tylko czas O (1) [stały]. Aby zapoznać się z odniesieniami do tych algorytmów, zobacz stronę z najniższym wspólnym problemem przodka w Wikipedii . (Podziękowania dla Jasona za pierwotne opublikowanie tego linku)
źródło
O(h)
występuje tylkoO(log(n))
wtedy, gdy drzewo jest zrównoważone. W przypadku dowolnego drzewa, binarnego lub nie, jeśli masz wskaźniki nadrzędne, możesz określić ścieżkę od liścia do korzenia wO(h)
czasie, po prostu podążając za wskaźnikiem nadrzędnym doh
czasu. To daje ci ścieżkę od liścia do korzenia. Jeśli ścieżki są przechowywane jako stos, iteracja stosu daje ścieżkę od katalogu głównego do liścia. Jeśli nie masz wskaźników nadrzędnych i nie masz specjalnej struktury drzewa, znalezienie ścieżki od korzenia do liścia zajmie trochęO(n)
czasu.Zaczynając od
root
węzła i idąc w dół, jeśli znajdziesz dowolny węzeł, który ma jedno z nichp
lubq
jako jego bezpośrednie dziecko potomne, jest to LCA. (edytuj - powinno to być, jeślip
lubq
jest wartością węzła, zwróć ją. W przeciwnym razie nie powiedzie się, gdy jeden z nichp
lubq
jest bezpośrednim dzieckiem drugiego).W przeciwnym razie, jeśli znajdziesz węzeł z
p
prawym (lub lewym) poddrzewem iq
lewym (lub prawym) poddrzewem, to jest to LCA.Naprawiony kod wygląda następująco:
Poniższy kod nie działa, gdy jeden z nich jest bezpośrednim dzieckiem innego.
Kod w akcji
źródło
Oto działający kod w JAVA
public static Node LCA(Node root, Node a, Node b) { if (root == null) { return null; } // If the root is one of a or b, then it is the LCA if (root == a || root == b) { return root; } Node left = LCA(root.left, a, b); Node right = LCA(root.right, a, b); // If both nodes lie in left or right then their LCA is in left or right, // Otherwise root is their LCA if (left != null && right != null) { return root; } return (left != null) ? left : right; }
źródło
Odpowiedzi udzielone do tej pory wykorzystują rekursję lub przechowują na przykład ścieżkę w pamięci.
Oba te podejścia mogą zawieść, jeśli masz bardzo głębokie drzewo.
Oto moje podejście do tego pytania. Gdy sprawdzimy głębokość (odległość od korzenia) obu węzłów, jeśli są równe, możemy bezpiecznie przejść w górę od obu węzłów w kierunku wspólnego przodka. Jeśli jedna z głębokości jest większa, powinniśmy iść w górę z głębszego węzła, pozostając w drugim.
Oto kod:
Złożoność czasowa tego algorytmu to: O (n). Przestrzenna złożoność tego algorytmu to: O (1).
Jeśli chodzi o obliczanie głębokości, możemy najpierw zapamiętać definicję: Jeśli v jest pierwiastkiem, głębokość (v) = 0; W przeciwnym razie głębokość (v) = głębokość (rodzic (v)) + 1. Możemy obliczyć głębokość w następujący sposób:
źródło
Cóż, ten rodzaj zależy od struktury Twojego drzewa binarnego. Prawdopodobnie masz jakiś sposób na znalezienie pożądanego węzła liścia, biorąc pod uwagę korzeń drzewa - po prostu zastosuj to do obu wartości, aż wybrane gałęzie się rozejdą.
Jeśli nie masz sposobu na znalezienie pożądanego liścia na podstawie korzenia, jedynym rozwiązaniem - zarówno podczas normalnej pracy, jak i znalezienia ostatniego wspólnego węzła - jest brutalne przeszukanie drzewa.
źródło
Można to znaleźć pod adresem : - http://goursaha.freeoda.com/DataStructure/LowestCommonAncestor.html
źródło
Algorytm najmniej wspólnych przodków w trybie off-line Tarjana jest wystarczająco dobry (por. Także Wikipedia ). Więcej na ten temat (najniższy wspólny problem przodków) można znaleźć w Wikipedii .
źródło
Aby znaleźć wspólnego przodka dwóch węzłów: -
To działałoby dla binarnego drzewa wyszukiwania.
źródło
Podjąłem próbę z ilustracjami i działającym kodem w Javie,
http://tech.bragboy.com/2010/02/least-common-ancestor-without-using.html
źródło
Poniższy algorytm rekurencyjny będzie działał w O (log N) dla zrównoważonego drzewa binarnego. Jeśli którykolwiek z węzłów przekazanych do funkcji getLCA () jest taki sam jak root, wówczas root będzie LCA i nie będzie potrzeby wykonywania żadnej rekusji.
Przypadki testowe. [1] Oba węzły n1 i n2 znajdują się w drzewie i znajdują się po obu stronach swojego węzła nadrzędnego. [2] Albo węzeł n1 albo n2 jest korzeniem, LCA jest korzeniem. [3] W drzewie znajduje się tylko n1 lub n2, LCA będzie albo węzłem głównym lewego poddrzewa korzenia drzewa, albo LCA będzie węzłem głównym prawego poddrzewa korzenia drzewa.
[4] Ani n1 ani n2 nie występuje w drzewie, nie ma LCA. [5] Zarówno n1, jak i n2 są w linii prostej obok siebie, LCA będzie równe n1 lub n2, które zawsze są zbliżone do korzenia drzewa.
źródło
Po prostu zejdź z całego drzewa
root
tak długo, jak długo oba podane węzły, powiedzmyp
iq
, dla którego należy znaleźć Przodka, znajdują się w tym samym poddrzewie (co oznacza, że ich wartości są mniejsze lub oba są większe od korzenia).To idzie prosto od korzenia do najmniejszego przodka, nie patrząc na resztę drzewa, więc jest prawie tak szybkie, jak to tylko możliwe. Na kilka sposobów.
w przypadku przepełnienia zrobiłbym (root.val - (long) p.val) * (root.val - (long) q.val)
źródło
źródło
Rozważ to drzewo
Jeśli wykonamy przeglądanie w trybie postorder i preorder i znajdziemy pierwszego występującego wspólnego poprzednika i następcę, otrzymamy wspólnego przodka.
zamówienie pocztowe => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,12,7 zamówienie wstępne => 7,3,1,0,2,6,4 , 5,12,9,8,11,10,13,15,14
Najmniejszy wspólny przodek 8,11
w zamówieniu pocztowym mamy => 9,14,15,13,12,7 po 8 i 11 w przedsprzedaży mamy => 7,3,1,0,2,6,4,5,12,9 przed 8 i 11
9 to pierwsza wspólna liczba występująca po 8 i 11 w zamówieniu pocztowym i przed 8 i 11 w przedsprzedaży, stąd 9 to odpowiedź
Najmniejszy wspólny przodek 5,10
11,9,14,15,13,12,7 w zamówieniu pocztowym 7,3,1,0,2,6,4 w przedsprzedaży
7 to pierwsza liczba, która pojawia się po 5,10 w zamówieniu pocztowym i przed 5,10 w zamówieniu w przedsprzedaży, stąd 7 to odpowiedź
źródło
Jeśli jest to pełne drzewo binarne z dziećmi węzła x jako 2 * x i 2 * x + 1, to można to zrobić szybciej
Jak to działa
To działa, ponieważ w zasadzie należy rekurencyjnie podzielić większą liczbę przez dwa, aż obie liczby będą równe. Ta liczba jest wspólnym przodkiem. Dzielenie jest w rzeczywistości właściwą zmianą. Musimy więc znaleźć wspólny przedrostek dwóch liczb, aby znaleźć najbliższego przodka
źródło
W scali możesz:
źródło
źródło
Oto sposób, w jaki można to zrobić w C ++. Starałem się, aby algorytm był jak najłatwiejszy do zrozumienia:
Jak tego użyć:
źródło
Najłatwiejszym sposobem znalezienia najniższego wspólnego przodka jest użycie następującego algorytmu:
public int LCA(TreeNode root, int value 1, int value 2) { while (root != null) { if (value1 < root.data && value2 < root.data) return LCA(root.left, value1, value2); else if (value2 > root.data && value2 2 root.data) return LCA(root.right, value1, value2); else return root } return null; }
źródło
Znalazłem rozwiązanie
W zależności od 3 przejść możesz zdecydować, kto jest LCA. Z LCA znajdź odległość obu węzłów. Dodaj te dwie odległości, co jest odpowiedzią.
źródło
Oto, co myślę,
Złożoność: krok 1: O (n), krok 2 = ~ O (n), łącznie = ~ O (n).
źródło
Oto dwa podejścia w języku C # (.net) (oba omówione powyżej) w celach informacyjnych:
Rekurencyjna wersja wyszukiwania LCA w drzewie binarnym (O (N) - tak jak odwiedzany jest co najwyżej każdy węzeł) (główne punkty rozwiązania to LCA to (a) jedyny węzeł w drzewie binarnym, w którym oba elementy znajdują się po obu stronach poddrzewa (po lewej i po prawej) to LCA. (b) Nie ma też znaczenia, który węzeł jest obecny po obu stronach - początkowo starałem się zachować te informacje i oczywiście funkcja rekurencyjna stała się tak zagmatwana.
Przeszukiwanie obu węzłów (O (N)) i śledzenie ścieżek (zajmuje dodatkową przestrzeń - więc numer 1 jest prawdopodobnie lepszy, nawet jeśli przestrzeń jest prawdopodobnie nieistotna, jeśli drzewo binarne jest dobrze zbalansowane, ponieważ wtedy dodatkowe zużycie pamięci będzie O (log (N)).
aby ścieżki były porównywane (zasadniczo podobne do akceptowanej odpowiedzi - ale ścieżki są obliczane przy założeniu, że węzeł wskaźnikowy nie występuje w węźle drzewa binarnego)
Tylko do wypełnienia ( nie dotyczy pytania ), LCA w BST (O (log (N))
Testy
Rekursywne:
gdzie powyższa prywatna wersja rekurencyjna jest wywoływana następującą metodą publiczną:
Rozwiązanie dzięki śledzeniu ścieżek obu węzłów:
gdzie FindNodeAndPath jest zdefiniowany jako
BST (LCA) - niepowiązane (tylko do uzupełnienia w celach informacyjnych)
Testy jednostkowe
źródło
Jeśli ktoś jest zainteresowany pseudokodem (do prac domowych na uczelni), oto jeden.
źródło
Chociaż już na to odpowiedziano, to jest moje podejście do tego problemu przy użyciu języka programowania C. Chociaż kod pokazuje binarne drzewo wyszukiwania (jeśli chodzi o insert ()), ale algorytm działa również dla drzewa binarnego. Pomysł polega na przejściu przez wszystkie węzły, które leżą od węzła A do węzła B w trakcie przechodzenia w kolejności, i wyszukanie dla nich indeksów w trakcie przechodzenia po zamówieniu. Węzeł z maksymalnym indeksem w przechodzeniu po zamówieniu jest najniższym wspólnym przodkiem.
To jest działający kod C do implementacji funkcji znajdującej najniższego wspólnego przodka w drzewie binarnym. Udostępniam również wszystkie funkcje narzędziowe itp., Ale w celu szybkiego zrozumienia przejdź do CommonAncestor ().
źródło
Może być jeszcze jedno podejście. Jednak nie jest tak skuteczny, jak sugerowano już w odpowiedziach.
Utwórz wektor ścieżki dla węzła n1.
Utwórz drugi wektor ścieżki dla węzła n2.
Wektor ścieżki implikujący zbiór węzłów z tego jednego przeszedłby, aby dotrzeć do danego węzła.
Porównaj oba wektory ścieżki. Indeks, w którym się nie zgadzają, zwraca węzeł w tym indeksie - 1. To dałoby LCA.
Wady tego podejścia:
Aby obliczyć wektory ścieżki, trzeba dwukrotnie przejść przez drzewo. Potrzebujesz dodatkowej przestrzeni O (h) do przechowywania wektorów ścieżek.
Jednak jest to również łatwe do wdrożenia i zrozumienia.
Kod do obliczania wektora ścieżki:
źródło
Spróbuj w ten sposób
źródło
Surowy sposób:
Problem z powyższą metodą polega na tym, że "znajdź" będziemy wykonywać wiele razy, tj. Istnieje możliwość, że każdy węzeł zostanie przemierzony wiele razy. Możemy rozwiązać ten problem, jeśli uda nam się zapisać informacje, aby nie przetwarzać ich ponownie (pomyśl o programowaniu dynamicznym).
Więc zamiast znajdować każdy węzeł, prowadzimy rejestr tego, co już zostało znalezione.
Lepszy sposób:
Kod:
źródło
Kod wyszukiwania szerokości, aby upewnić się, że oba węzły znajdują się w drzewie. Dopiero wtedy przejdź do wyszukiwania LCA. Prosimy o komentarz, jeśli masz jakieś sugestie dotyczące ulepszeń. Myślę, że prawdopodobnie możemy oznaczyć je jako odwiedzone i ponownie rozpocząć wyszukiwanie w pewnym momencie, w którym przerwaliśmy, aby poprawić drugi węzeł (jeśli nie zostanie znaleziony ODWIEDZONY)
źródło
Masz rację, że bez węzła nadrzędnego rozwiązanie z przechodzeniem da ci złożoność czasową O (n).
Podejście przemierzające Załóżmy, że znajdujesz LCA dla węzłów A i B, najprostszym podejściem jest najpierw pobranie ścieżki od katalogu głównego do A, a następnie ścieżki od katalogu głównego do B. Gdy masz już te dwie ścieżki, możesz je łatwo iterować i znajdź ostatni wspólny węzeł, który jest najniższym wspólnym przodkiem A i B.
Rozwiązanie rekurencyjne Innym podejściem jest użycie rekurencji. Po pierwsze, możemy pobrać LCA z lewego i prawego drzewa (jeśli istnieje). Jeśli jeden z A lub B jest węzłem głównym, to korzeń jest LCA i po prostu zwracamy pierwiastek, który jest punktem końcowym rekurencji. Kiedy będziemy dalej dzielić drzewo na podgrupy, w końcu trafimy na A i B.
Aby połączyć rozwiązania podproblemów, jeśli LCA (lewe drzewo) zwraca węzeł, wiemy, że zarówno A, jak i B znajdują się w lewym drzewie, a zwrócony węzeł jest wynikiem końcowym. Jeśli zarówno LCA (lewy), jak i LCA (prawy) zwracają niepuste węzły, oznacza to, że A i B znajdują się odpowiednio w lewym i prawym drzewie. W tym przypadku węzeł główny jest najniższym wspólnym węzłem.
Sprawdź Najniższy wspólny przodek, aby uzyskać szczegółową analizę i rozwiązanie.
źródło
Niektóre rozwiązania zakładają, że istnieje odniesienie do węzła głównego, inne zakładają, że drzewo jest BST. Udostępnianie mojego rozwiązania za pomocą hashmap, bez odniesienia do
root
węzła i drzewa, może być BST lub inne niż BST:źródło
Rozwiązanie 1: rekurencyjne - szybciej
Rozwiązanie 2: Iteracyjne - Korzystanie ze wskaźników nadrzędnych - Wolniej
źródło