Znalezienie wspólnego przodka w binarnym drzewie

głosy
7

To pytanie zadano mi w wywiadzie: mam binarne drzewo i muszę znaleźć wspólny przodek (jednostka dominująca) otrzymuje dwie losowe węzły tego drzewa. Jestem również biorąc pod uwagę wskaźnik do węzła głównego.


Moja odpowiedź brzmi:

Przemierzać drzewo oddzielnie dla obu węzłów aż dojdziesz węzeł, który jest oczekiwany. Równolegle podczas przechodzenia przechowywać element, a następnego adresu w połączonej listy. Następnie mamy dwa połączone list z nami. Więc spróbuj porównanie dwóch połączonych list i ostatni wspólny węzeł w obu połączonych listach jest rodzicem.

Mam na myśli, że to rozwiązanie jest poprawne, poprawcie mnie jeśli się mylę. Jeśli to rozwiązanie jest poprawne, może wiem czy to jest tylko lepszym rozwiązaniem dla tego zadania czy jest jakieś inne lepsze rozwiązanie niż to!

Utwórz 30/05/2011 o 11:18
źródło użytkownik
W innych językach...                            


10 odpowiedzi

głosy
2

Zrób przechodzenie poziom zamówień, a dla każdego węzła spotykamy możemy sprawdzić swoje dzieci. Jeżeli są one przewidziane losowe węzłów, wówczas węzeł przodek został znaleziony.

Edit1:

Oto zarys

struct _node {
   my_type data;
   struct _node *left;
   struct _node *right;
}

q = queue_create ();
queue_insert (q, head);
temp = head;
while (!empty (q))
{
    temp = queue_remove (q);
 if (
      (temp->left == my_random_node_1) && (head->right == my_random_node_2) ||
      (temp->left == my_random_node_2) && (head->right == my_random_node_1)
    )
    {
       /* temp is the common parent of the two target notes */
       /* Do stuffs you need to do */
    }

    /* Enqueue the childs, so that in successive iterations we can
     * check them, by taking out from the queue
     */
    push (q, temp->left);
    push (q, temp->right);
}

AKTUALIZACJA

Poprzedni algorytm znajdzie tylko wspólnych rodziców (bezpośrednim przodkiem), dlatego jeśli dwie losowo wybrane węzły, jeśli nie jesteś dzieckiem wspólnego rodzica nie będzie można znaleźć odpowiedź.

Algorytm poniżej znajdzie wspólnych przodków, a nie tylko rodziców.

Myślę, że następujący algorytm będzie działać:

Zrób przechodzenie postorder drzewa binarnego, i znaleźć dla losowego węźle 1 r1, jeżeli stwierdzimy, że następnie oznaczyć ją w zmiennej państwowego być w stanie jednym , i zachować znalezienie dla drugiego węzła, jeśli okaże następnie zaktualizować zmienną stanu do stan dwóch i zatrzymać szukają bardziej i powrotu. Zmienna państwo powinno być przekazywane przez każdego węzła do swoich rodziców (rekurencyjnie). Pierwszy węzeł, który napotyka zmiennej stanu w stan dwóch jest wspólnym przodkiem.

Implementacja algorytmu jest następujący:

int postorder (node *p, int r1, int r2)
{
  int x = 0; /* The state variable */
  if (p->data == TERMINAL_VAL)
    return x;

  /* 0x01 | 0x02 = 0x03 threfore 
   * state one is when x = 0x01 or x = 0x02
   * state two is when x = 0x03
   */
  if (p->data == r1)
    x |= 0x01;
  else if (p->data == r2)
    x |= 0x02;

  /* if we have x in state two, no need to search more
   */
  if (x != 0x03)
    x |= postorder (p->left, r1, r2);
  if (x != 0x03)
    x |= postorder (p->right, r1, r2);

  /* In this node we are in state two, print node if this node
   * is not any of the two nodes r1 and r2. This makes sure that
   * is one random node is an ancestor of another random node
   * then it will not be printed instead its parent will be printed
   */
  if ((x == 0x03) && (p->data != r1) && (p->data != r2))
  {
   printf ("[%c] ", p->data);
   /* set state variable to 0 if we do not want to print 
    * the ancestors of the first ancestor 
    */
   x = 0;
  }

  /* return state variable to parent
   */    
  return x;
}

Myślę, że to będzie działać poprawnie, choć nadal jestem do udowodnienia poprawności algorytmu jest. Jest jedna wada, która jest, jeśli jeden węzeł jest dzieckiem innego węzła, a następnie będzie drukować tylko węzeł, który jest rodzicem drugiego, zamiast drukować rodzica z nich. Jeżeli jeden z węzła losowej jest przodkiem innego losowego węzła wtedy zamiast drukować losowe węzeł przodka, to wydrukować rodzica niego. W przypadku, w którym jeden z węzła losowej jest węzeł główny, wydrukuje nic, jak to zawsze jest przodkiem innego węzła losowej, a zatem ich wspólny przodek nie istnieje. W tym szczególnym przypadku, funkcja zwróci 0x03się maini może być wykryty.

Ponieważ algorytm robi przechodzenie postorder dlatego wymaga O (n), a tym samym wykonanie O (n) w pamięci. Również jako poszukiwanie zatrzymuje jak wkrótce oba węzły są znalezione, płytsze węzły szybciej poszukiwanie kończy.

AKTUALIZACJA

Oto niektóre dyskusje mode: Jak znaleźć najmniejszy wspólny przodek dwa węzły w każdym drzewie binarnym?

Odpowiedział 30/05/2011 o 11:23
źródło użytkownik

głosy
0

@Above, to nie będzie działać, ponieważ są przy założeniu, że oba węzły są natychmiastowe dziecko z jakiegoś konkretnego węzła ...

            8
     10           12
 7             

i dałem węzły jako 7 i 12, odpowiedź musi być 8. Zróbmy tak

    find(root, d1, d2, n1=null, n2=null)
     {
          if(n1 && n2) return;
          if(!root) return;

          else  if(root -> d == d1 ) n1 = root;
          else  if(root -> d == d2 ) n2 = root;                     
          find(root->left, d1, d2, n1, n2);
          find(root->right, d1, d2, n1, n2);
     }

     LCA(root, d1, d2)
     {
         node *n1=null, *n2=null;
         find(root, d1, d2, n1, n2);
         if(n1 == null || n2 == null )error 'nodes not present' exit(0);
         findIntersect(n1, n2); 
     }
     findInterSect(node *n1, node *n2)
     {
        l1 = length(n1);
        l2 = length(n2);
        node *g = n2, *l = n1;
        diff = abs(l1 - l2);
        if(l1>l2) g = n1 l =n2 
        while(diff) g = g->parent; diff--;
        // now both nodes are at same level
        while(g != l) g= g->parent, l = l->parent;
     }
Odpowiedział 30/08/2011 o 17:29
źródło użytkownik

głosy
0

Pseudo kod:

node *FindCommonAncestor(node *root, node *node1, node *node2) {
  node *current = node1;
  node_list temp_list;
  temp_list.add(current);
  while (current != root) {
    current = current.parent;
    temp_list.add(current);
  }
  current = node2;
  while (current not in temp_list) {
    current = current.parent;
  }
  return current;
}

Jeśli węzły są zdecydowanie częścią tego samego drzewa, a potem oni na pewno mają wspólnego przodka (nawet jeśli jest to korzeń w najgorszym przypadku). Tak będzie zawsze kończą i nie ma warunek błędu martwić.

Pierwsze biegnie pętli n razy, gdzie n jest głębokość node1, więc O (n). Druga pętla jest czas, gdzie M w głębi node2. Odnośnika do listy TEMP (w najgorszym) n. Tak więc druga pętla O (M * n) i dominuje więc funkcja przebiega w O M * (n).

Jeśli używasz struktury danych dobry zestaw (np hash table) do przestrzeni temp zamiast listy, można wyciąć odnośnika do (zwykle) O (1), bez zwiększania kosztów dodawania węzłów do temp. Zmniejsza to naszej funkcji czasu O (m).

Przestrzeń wymagana O (n), w obu kierunkach.

Ponieważ nie wiemy, N i M z wyprzedzeniem, postawmy go pod względem całkowitej liczby węzłów w drzewie: S. Jeśli drzewo jest zrównoważony, to n i m są każdorazowo ograniczona log_2 (S), tak czas pracy wynosi O (log_2 (S) ^ 2). Log_2 jest dość silny, więc S musiałby dostać dość duży, zanim będę martwić się o sile 2. Jeżeli drzewo nie jest zrównoważony, to tracimy log_2 (drzewo rzeczywiście może przerodzić się w połączonej listy). Tak najgorszego przypadku (przy jeden węzeł jest głównym a drugie jest liści całkowicie zdegenerowany drzewa) O (S ^ 2).

Odpowiedział 30/08/2011 o 18:15
źródło użytkownik

głosy
6

Ustaw kursor w obu losowych węzłów. Znajdź głębię każdego węzła przez przejeżdżające do góry i liczenia odległości od węzła głównego. Następnie ustaw kursor w obu węzłach ponownie. Dla głębszego węźle przechodzić aż oba wskaźniki są na tej samej głębokości. Następnie przemierzać się na obu węzłach aż wskaźniki wskazują na samym węźle. To jest węzeł przodek.

Przez „przechodzić w górę” Ja po prostu oznaczać, przesuń wskaźnik do rodzica bieżącego węzła.

Edycja wyjaśnienie: Kluczową ideą jest to, że gdy oba węzły są na tej samej głębokości, można znaleźć wspólnego rodzica bardzo szybko tylko poprzez proste przechodzenie. Więc wspinać dolna aż oba są na tej samej głębokości, a następnie przechodzić do góry. Niestety ja naprawdę nie wiem, czy bym C napisać kod, ale to algorytm powinien odpowiedzieć na to pytanie.

Edycja ponownie: A moja metoda działa w czasie O (log (n)) czasu i O (1) pamięci.

Kolejny edit: O (log (n)) w zrównoważonym drzewie. Wydajność najgorszy O (n) w przypadku niezrównoważonego drzewa. Dzięki @DaveCahill

Odpowiedział 30/08/2011 o 20:15
źródło użytkownik

głosy
1

Problem ten został bardzo dobrze zbadane i nie są znane algorytmy, które można rozwiązać w czasie liniowym. Niniejszy dokument opisuje wiele różnych metod można użyć, aby go rozwiązać. Admittedtly jest to praca naukowa, a więc algorytmy są nieco skomplikowane, ale niektóre z opisywanych metod są całkiem możliwe.

Odpowiedział 30/08/2011 o 20:47
źródło użytkownik

głosy
7

Może głupie podejście:

Generowanie ścieżkę przepływu z każdego węzła do korzenia, przechowywania jako ciąg „L” i „R”.

Odwrócić te sznurki. Wziąć najdłuższy wspólny przedrostek - teraz masz ścieżkę do wspólnego przodka.

Odpowiedział 30/08/2011 o 22:21
źródło użytkownik

głosy
0
  1. Pre order przechodzenie chyba kogokolwiek 1 węzła jest spełniony i zapisać węzły odwiedził uptil teraz.

  2. Inorder przechodzenie, zacznij oszczędzać węzły gdy każdy 1 (z dwóch przewidzianych węzły) węzeł jest spełniony, i zapisać listę do następnego węzła jest spełniony.

  3. przechodzenie postu, zacznij oszczędzać węzły gdy oba węzły hav odwiedził ...
               ZA         
      pne         
  DEFG       
HIJKLMNO     

Załóżmy, H i E są dwie losowe węzłów.

  1. ABDH
  2. HDIBJE
  3. EBLMENOGCA

Znajdź pierwszy węzeł wspólnego we wszystkich trzech ...

Odpowiedział 15/01/2012 o 15:55
źródło użytkownik

głosy
3

Myślę, że można po prostu zrobić wyszukiwania jednocześnie na obu węzłach; punkt, w którym poszukiwanie odbiega to wspólny przodek.

commonAncestor tree a b:
  value := <value of node 'tree'>
  if (a < value) && (b < value)
  then commonAncestor (left tree) a b
  else if (a > value) && (b > value)
  then commonAncestor (right tree) a b
  else tree

Co ciekawe takie podejście byłoby skalować do więcej niż dwóch węzłów (sprawdź wszystkie z nich będzie po lewej stronie tree, itd.)

Odpowiedział 05/02/2012 o 06:18
źródło użytkownik

głosy
0

hi to powróci najniższą wartość węzła przodka, gdzie korzeń drzewa i val1, wart2 - wartości> danych dla węzłów są przekazywane

int CommonAncestor(node *root, int val1,int val2) 
{

    if(root == NULL || (! root->left && ! root->right  )
        return false;

        while(root)
        {
            if(root->data < val1 && root->data < val2)
             {
                root = root->left;
             }
             else if(root->data > val1 && root->data > val2)
            {
                root= root->right;
            }
            else
              return root->data;      
        }
}
Odpowiedział 25/09/2012 o 11:57
źródło użytkownik

głosy
0

Oto dwa podejścia w języku C # (.NET) (oba omówione powyżej) dla odniesienia:

  1. Rekurencyjne wersja znalezienia LCA w drzewo binarne (O (n) - co najwyżej każdy węzeł odwiedza) (główne punkty roztworu LCA (a) tylko węzeł drzewo binarne, w którym oba elementy znajdować się po obu stronach z poddrzewach (lewa i po prawej) jest LCA. (b) a także nie ma znaczenia, który węzeł jest obecny po obu stronach - początkowo starałem się zachować te informacje, i oczywiście funkcja rekurencyjna się tak mylące raz zdałem sobie sprawę, że stał się bardzo elegancko..

  2. Poszukiwania obu węzłach (O (n)), oraz śledzenie ścieżek (wykorzystuje dodatkową przestrzeń - tak, # 1 jest chyba lepszy nawet, że przestrzeń jest prawdopodobnie znikomy jeśli drzewo binarne jest dobrze wyważony jak wtedy dodatkowo zużycie pamięci będzie tylko w O (log (n)).

    tak, że ścieżki są porównywane (essentailly podobny do przyjętego odpowiedź - ale drogi jest obliczana przy założeniu, że węzeł wskaźnik nie jest obecny w binarnym węzła drzewa)

  3. Tylko na zakończenie ( nie związane na pytanie ), LCA w BST (O (log (n))

  4. testy

rekurencyjne:

private BinaryTreeNode LeastCommonAncestorUsingRecursion(BinaryTreeNode treeNode, 
            int e1, int e2)
        {
            Debug.Assert(e1 != e2);

            if(treeNode == null)
            {
                return null;
            }
            if((treeNode.Element == e1)
                || (treeNode.Element == e2))
            {
                //we don't care which element is present (e1 or e2), we just need to check 
                //if one of them is there
                return treeNode;
            }
            var nLeft = this.LeastCommonAncestorUsingRecursion(treeNode.Left, e1, e2);
            var nRight = this.LeastCommonAncestorUsingRecursion(treeNode.Right, e1, e2);
            if(nLeft != null && nRight != null)
            {
                //note that this condition will be true only at least common ancestor
                return treeNode;
            }
            else if(nLeft != null)
            {
                return nLeft;
            }
            else if(nRight != null)
            {
                return nRight;
            }
            return null;
        }

gdzie nad prywatnym wersji rekurencyjnej jest wywoływana przez następującą metodą publicznego:

public BinaryTreeNode LeastCommonAncestorUsingRecursion(int e1, int e2)
        {
            var n = this.FindNode(this._root, e1);
            if(null == n)
            {
                throw new Exception("Element not found: " + e1);
            }
            if (e1 == e2)
            {   
                return n;
            }
            n = this.FindNode(this._root, e2);
            if (null == n)
            {
                throw new Exception("Element not found: " + e2);
            }
            var node = this.LeastCommonAncestorUsingRecursion(this._root, e1, e2);
            if (null == node)
            {
                throw new Exception(string.Format("Least common ancenstor not found for the given elements: {0},{1}", e1, e2));
            }
            return node;
        }

Rozwiązanie przez śledzenie ścieżek obu węzłach:

public BinaryTreeNode LeastCommonAncestorUsingPaths(int e1, int e2)
        {
            var path1 = new List<BinaryTreeNode>();
            var node1 = this.FindNodeAndPath(this._root, e1, path1);
            if(node1 == null)
            {
                throw new Exception(string.Format("Element {0} is not found", e1));
            }
            if(e1 == e2)
            {
                return node1;
            }
            List<BinaryTreeNode> path2 = new List<BinaryTreeNode>();
            var node2 = this.FindNodeAndPath(this._root, e2, path2);
            if (node1 == null)
            {
                throw new Exception(string.Format("Element {0} is not found", e2));
            }
            BinaryTreeNode lca = null;
            Debug.Assert(path1[0] == this._root);
            Debug.Assert(path2[0] == this._root);
            int i = 0;
            while((i < path1.Count)
                && (i < path2.Count)
                && (path2[i] == path1[i]))
            {
                lca = path1[i];
                i++;
            }
            Debug.Assert(null != lca);
            return lca;
        }

gdzie FindNodeAndPath jest zdefiniowany jako

private BinaryTreeNode FindNodeAndPath(BinaryTreeNode node, int e, List<BinaryTreeNode> path)
        {
            if(node == null)
            {
                return null;
            }
            if(node.Element == e)
            {
                path.Add(node);
                return node;
            }
            var n = this.FindNodeAndPath(node.Left, e, path);
            if(n == null)
            {
                n = this.FindNodeAndPath(node.Right, e, path);
            }
            if(n != null)
            {
                path.Insert(0, node);
                return n;
            }
            return null;
        }

BST (LCA) - nie związane (tylko do ukończenia przez odniesienie)

public BinaryTreeNode BstLeastCommonAncestor(int e1, int e2)
        {
            //ensure both elements are there in the bst
            var n1 = this.BstFind(e1, throwIfNotFound: true);
            if(e1 == e2)
            {
                return n1;
            }
            this.BstFind(e2, throwIfNotFound: true);
            BinaryTreeNode leastCommonAcncestor = this._root;
            var iterativeNode = this._root;
            while(iterativeNode != null)
            {
                if((iterativeNode.Element > e1 ) && (iterativeNode.Element > e2))
                {
                    iterativeNode = iterativeNode.Left;
                }
                else if((iterativeNode.Element < e1) && (iterativeNode.Element < e2))
                {
                    iterativeNode = iterativeNode.Right;
                }
                else
                {
                    //i.e; either iterative node is equal to e1 or e2 or in between e1 and e2
                    return iterativeNode;
                }
            }
            //control will never come here
            return leastCommonAcncestor;
        }

Testy jednostkowe

[TestMethod]
        public void LeastCommonAncestorTests()
        {
            int[] a = { 13, 2, 18, 1, 5, 17, 20, 3, 6, 16, 21, 4, 14, 15, 25, 22, 24 };
            int[] b = { 13, 13, 13, 2, 13, 18, 13, 5, 13, 18, 13, 13, 14, 18, 25, 22};
            BinarySearchTree bst = new BinarySearchTree();
            foreach (int e in a)
            {
                bst.Add(e);
                bst.Delete(e);
                bst.Add(e);
            }
            for(int i = 0; i < b.Length; i++)
            {
                var n = bst.BstLeastCommonAncestor(a[i], a[i + 1]);
                Assert.IsTrue(n.Element == b[i]);
                var n1 = bst.LeastCommonAncestorUsingPaths(a[i], a[i + 1]);
                Assert.IsTrue(n1.Element == b[i]);
                Assert.IsTrue(n == n1);
                var n2 = bst.LeastCommonAncestorUsingRecursion(a[i], a[i + 1]);
                Assert.IsTrue(n2.Element == b[i]);
                Assert.IsTrue(n2 == n1);
                Assert.IsTrue(n2 == n);
            }
        }
Odpowiedział 14/07/2014 o 14:02
źródło użytkownik

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more