Găsirea strămoșul comun într-un arbore binar

voturi
7

Această întrebare a fost rugat să-mi într-un interviu: Am un arbore binar și am să găsesc strămoș comun (părinte) dat două noduri aleatoare ale acestui copac. Sunt, de asemenea, dat un pointer la nodul rădăcină.


Răspunsul meu este:

Traverse copac separat pentru ambele nodurile până când ajunge la nodul care este de așteptat. Paralel în timp ce traversează magazin elementul și adresa următoare într-o listă legată. Apoi, avem două liste de legătură cu noi. Deci, încercați să comparați cele două liste legate și ultimul nod comun în ambele liste legate este mamă.

Mă gândesc că această soluție este corectă, corectați-mă dacă mă înșel. Dacă această soluție este corectă, poate că știu este aceasta singura soluție mai bună pentru această sarcină sau există orice altă soluție mai bună decât asta!

Întrebat 30/05/2011 la 11:18
sursa de către utilizator
În alte limbi...                            


10 răspunsuri

voturi
2

Faceti o traversal ordine de nivel, iar pentru fiecare nod vom întâlni, vom verifica copiii săi. În cazul în care acestea sunt furnizate noduri aleatoare, atunci nodul strămoș este găsit.

edit1:

Aici este o schiță

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);
}

ACTUALIZAȚI

Algoritmul anterior va găsi doar părinții comuni (strămoș direct), prin urmare, în cazul în care două noduri selectate aleatoriu în cazul în care nu sunt un copil de părinte comun nici un răspuns ar fi găsit.

Algoritmul de mai jos se va găsi strămoși comuni și nu numai părinții.

Cred că următorul algoritm va funcționa:

Faceti o traversal postordine a arborelui binar, și pentru a găsi pentru nodul aleatoare 1 r1, dacă vom găsi apoi marcați într - o variabilă de stat să fie într - un singur stat , și să păstreze găsirea pentru al doilea nod, dacă este găsit apoi actualizați variabila de stat la stat doi , și se va opri căutarea mai și să se întoarcă. Variabila de stat ar trebui să fie trecut prin fiecare nod la părinții săi (recursiv). Primul nod care se confruntă cu variabila de stat în stat doi este strămoșul comun.

Punerea în aplicare a algoritmului este următoarea:

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;
}

Cred că acest lucru va funcționa corect, deși sunt în continuare pentru a demonstra corectitudinea algoritmului. Există un dezavantaj, care este, în cazul în care un nod este un copil al unui alt nod, atunci se va imprima doar nodul care este părintele celuilalt, în loc să imprime părintelui lor. Dacă unul dintre nodul aleator este un strămoș al unui alt nod aleator , apoi în loc de imprimare nod aleatoare strămoș, se va imprima părintele acesteia. În cazul în care unul dintre nodul aleatoare este nodul rădăcină, se va imprima nimic, așa cum este întotdeauna stramosul celălalt nod aleatoriu, și , prin urmare , strămoșul lor comun nu există. În acest caz special , funcția va reveni 0x03în mainși poate fi detectat.

Cum acest algoritm are o traversal postordine, prin urmare, este nevoie de O (n) timpul de execuție și, astfel, O (n) memorie. De asemenea, ca și căutarea se oprește, cât mai curând, atât nodurile se găsesc, mai puțin adânc nodurile cu atât mai repede se termină căutarea.

ACTUALIZAȚI

Iată câteva discuții modul: Cum de a găsi cel mai mic strămoș comun a două noduri în orice arbore binar?

Publicat 30/05/2011 la 11:23
sursa de către utilizator

voturi
0

@Above, acest lucru nu va funcționa, pentru că sunteți presupunând că ambele sunt noduri copil imediate a unor anume nod ...

            8
     10           12
 7             

și am dat nodurile ca 7 și 12, răspunsul trebuie să fie 8. Să facă așa

    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;
     }
Publicat 30/08/2011 la 17:29
sursa de către utilizator

voturi
0

Pseudo cod:

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;
}

În cazul în care nodurile sunt cu siguranta parte din același copac, atunci ei vor avea cu siguranță un strămoș comun (chiar dacă este rădăcina în cel mai rău caz). Așa că se va termina întotdeauna și nu există nici o condiție de eroare să vă faceți griji.

Primele se execută în buclă n ori, unde n este adâncimea node1, deci este O (n). Cea de a doua buclă se execută ori m, unde m în adâncimea node2. Lookup în lista temp este (cel mai rău caz) n. Deci, a doua buclă este O (m * n), și domină, deci funcția se execută în O (m * n).

Dacă utilizați o structură de date bun set (de exemplu, un tabel hash) pentru spațiul de temperatură în loc de o listă, aveți posibilitatea să taie de căutare pentru a (de obicei) O (1), fără a crește costul de a adăuga noduri la temp. Acest lucru reduce timpul nostru funcția la O (m).

Cerința de spațiu este O (n), fie un fel.

Din moment ce nu știm n și m înainte de timp, să-l pună în ceea ce privește numărul total de noduri din arbore: S. Dacă arborele este echilibrat, atunci n și m sunt delimitate de fiecare log_2 (S), așa timpul de rulare este O (log_2 (S) ^ 2). Log_2 este destul de puternic, astfel încât S-ar trebui să obține destul de mare înainte de a mi-ar face griji cu privire la puterea 2. În cazul în care arborele nu este echilibrat, atunci vom pierde log_2 (copacul ar putea degenera de fapt, într-o listă legată). Deci, cel mai rău caz absolut (atunci când un nod este rădăcina, iar celălalt este frunza unui copac complet degenerat) este O (S ^ 2).

Publicat 30/08/2011 la 18:15
sursa de către utilizator

voturi
6

Setați un pointer la ambele noduri aleatoare. Găsiți adâncimea fiecărui nod traversand la partea de sus și de numărare distanța de la nodul rădăcină. Apoi, setați indicatorul la ambele noduri din nou. Pentru nodul mai adânc, traversează în sus până când ambele sunt indicii la aceeași adâncime. Apoi, pentru a traversa ambele noduri până când pointerii indică același nod. Acesta este nodul strămoș.

Prin „traversa în sus“ doar mă refer muta indicatorul la părintele nodului curent.

Editare pentru a clarifica: Ideea cheie este că , atunci când ambele noduri sunt la aceeași adâncime, puteți găsi comună părinte foarte repede doar prin simpla traversal. Deci ai urca pe cel inferior , până când ambele sunt la aceeași adâncime, și apoi traversa în sus. Îmi pare rău că nu știu cu adevărat C sau aș scrie cod, dar că algoritmul ar trebui să răspundă la întrebarea dumneavoastră.

Editați din nou: Și metoda mea rulează în O (log (n)) timp și O (1) de memorie.

O altă edita: O (log (n)) într - un arbore echilibrat. Performanță cel mai rău caz este O (n) pentru un arbore dezechilibrat. Multumesc @DaveCahill

Publicat 30/08/2011 la 20:15
sursa de către utilizator

voturi
1

Această problemă a fost foarte bine studiat și există algoritmi cunoscuți care pot rezolva în timp liniar. Lucrarea descrie mai multe abordări diferite pe care le puteți utiliza pentru a rezolva problema. Admittedtly este o lucrare de cercetare și așa algoritmii sunt un pic complicat, dar unele dintre abordările pe care le descrie sunt de fapt destul de fezabile.

Publicat 30/08/2011 la 20:47
sursa de către utilizator

voturi
7

Abordarea Poate prostie:

Genera calea de la fiecare nod la rădăcină, stocarea acestuia ca un șir de „L“ și „R“.

Reverse aceste siruri de caractere. Ia cel mai lung prefix comun - aveți acum calea către stramosul comun.

Publicat 30/08/2011 la 22:21
sursa de către utilizator

voturi
0
  1. Pre Traversarea comanda excepția cazului în care oricare 1 al nodului este îndeplinită și de a salva nodurile vizitate uptil acum.

  2. Inorder parcurgeri, începe salvarea nodurile atunci când oricare 1 (din două noduri furnizate) nodul este îndeplinită, și de a salva lista până când este îndeplinită nodul următor.

  3. posta comanda parcurgeri, începe salvarea noduri atunci când ambele noduri hav a fost vizitat ...
               A         
      BC         
  DEFG       
HIJKLMNO     

Să presupunem că H și E sunt două noduri aleatoare.

  1. ABDH
  2. HDIBJE
  3. EBLMENOGCA

Găsiți primul nod comun în toate cele trei ...

Publicat 15/01/2012 la 15:55
sursa de către utilizator

voturi
3

Cred că ai putea face doar o căutare simultan pentru ambele noduri; punctul în care căutarea diverge este strămoșul comun.

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

Interesant este această abordare ar scară la mai mult de două noduri (verificați toate acestea să fie pe partea stângă a treeetc.)

Publicat 05/02/2012 la 06:18
sursa de către utilizator

voturi
0

hi acest lucru se va întoarce cea mai mică valoare nod în cazul în care strămoșul rădăcină de copac și VAL1, val2 - valori> date pentru nodurile sunt transmise

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;      
        }
}
Publicat 25/09/2012 la 11:57
sursa de către utilizator

voturi
0

Aici sunt două abordări în c # (.net) (ambele discutate mai sus) pentru referință:

  1. Versiunea recursivă a găsi LCA în arbore binar (O (N) - ca cel mult fiecare nod este vizitat) (principalele puncte ale soluției este LCA este (a) nod numai în arbore binar în care ambele elemente locuiesc fiecare parte a subramificații (stânga și dreapta) este LCA. (b) şi , de asemenea , nu contează ce nod este prezent în ambele părți - inițial am încercat să păstreze aceste informații, și , evident , funcția recursiv devenit atât de confuz o dată l - am dat seama, a devenit foarte elegant..

  2. Căutarea ambele noduri (O (N)), și urmărirea căi (folosește spațiu suplimentar - deci, # 1 este, probabil, superioară chiar a crezut că spațiul este probabil neglijabil în cazul în care arborele binar este echilibrat ca și atunci consumul de memorie suplimentar va fi doar în O (log (N)).

    astfel încât căile sunt comparate (essentailly similar cu răspunsul acceptat - dar căile este calculat presupunând nodul pointer nu este prezent în nodul arbore binar)

  3. Doar pentru finalizarea ( nu are legătură cu întrebarea ), LCA în BST (O (log (N))

  4. teste

recursive:

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;
        }

în cazul în care versiunea de mai sus recursiv privată este invocată prin următoarea metodă publică:

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;
        }

Soluția de rezolvare prin urmărirea căi de ambele noduri:

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;
        }

unde FindNodeAndPath este definit ca

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) - nu au legătură (doar pentru finalizare pentru referință)

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;
        }

Teste unitate

[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);
            }
        }
Publicat 14/07/2014 la 14:02
sursa de către utilizator

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