Trovare l'antenato comune in un albero binario

voti
7

Questa domanda è stato chiesto di me in un'intervista: Ho un albero binario e devo trovare l'antenato comune (genitore) dato due nodi casuali di quell'albero. Sono anche dato un puntatore al nodo radice.


La mia risposta è:

Attraversare l'albero separatamente per entrambi i nodi fino a raggiungere il nodo che ci si aspetta. Parallel mentre attraversa negozio l'elemento e l'indirizzo successivo in una lista collegata. Poi abbiamo due liste collegate con noi. Quindi provare a confronto le due liste collegate e l'ultimo nodo comune in entrambe le liste collegate è il genitore.

Sto pensando che questa soluzione è corretta, mi corregga se sbaglio. Se questa soluzione è corretta, posso conosco è questa l'unica soluzione migliore per questo compito o c'è qualche altra soluzione migliore di questo!

È pubblicato 30/05/2011 alle 11:18
fonte dall'utente
In altre lingue...                            


10 risposte

voti
2

Fare un attraversamento ordine di livello, e per ogni nodo che incontriamo, controlliamo i suoi figli. Se sono forniti i nodi casuali, allora il nodo antenato è trovato.

Edit1:

Ecco una descrizione

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

AGGIORNARE

L'algoritmo precedente troverà solo i genitori comuni (negli antenati), quindi se due nodi scelti a caso, se non sono un figlio di genitore comune nessuna risposta sarebbe stata trovata.

L'algoritmo di seguito troverà antenati comuni e non solo i genitori.

Credo che il seguente algoritmo funziona:

Fare un attraversamento postorder del albero binario, e trovare per il nodo casuale 1 r1, se lo troviamo poi segnare in una variabile di stato di essere in uno stato , e continuare a trovare per il secondo nodo, se trovato quindi aggiornare la variabile di stato a stato a due , e interrompere la ricerca di più e tornare. La variabile di stato dovrebbe essere superato da tutti i nodi ai suoi genitori (ricorsivamente). Il primo nodo che incontra variabile di stato in stato due è il capostipite.

L'implementazione dell'algoritmo è la seguente:

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

Penso che questo funziona correttamente, anche se sono ancora da dimostrare la correttezza dell'algoritmo. V'è uno svantaggio, che è, se un nodo è un figlio di un altro nodo, allora sarà stampato solo il nodo che è il genitore dell'altro, invece di stampare il genitore di loro. Se uno dei nodi casuali è un antenato di un altro nodo a caso poi invece di stampare il nodo casuale antenato, verrà stampata la madre di esso. Nel caso in cui uno dei nodo casuale è il nodo radice, stamperà nulla, in quanto è sempre l'antenato dell'altro nodo casuale, e quindi la loro antenato comune non esiste. In questo caso particolare la funzione restituisce 0x03in maine può essere rilevato.

Poiché questo algoritmo fa un attraversamento postorder quindi richiede O (n) tempo di esecuzione e quindi O (n) della memoria. Inoltre, come la ricerca si ferma non appena sia i nodi si trovano, il meno profondo i nodi più rapidamente la ricerca termina.

AGGIORNARE

Qui ci sono alcune discussioni di modalità: Come il più basso antenato comune di due nodi, in ogni albero binario?

Risposto il 30/05/2011 a 11:23
fonte dall'utente

voti
0

@Above, questo non funzionerà, perché si stanno assumendo che entrambi i nodi sono figlio diretto di qualche nodo particolare ...

            8
     10           12
 7             

e ho dato i nodi da 7 e 12, la risposta deve essere 8. Consente di fare in questo modo

    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;
     }
Risposto il 30/08/2011 a 17:29
fonte dall'utente

voti
0

pseudocodice:

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

Se i nodi sono sicuramente parte dello stesso albero, poi faranno sicuramente hanno un antenato comune (anche se è la radice nel peggiore dei casi). Così sarà sempre interrompere e non c'è alcuna condizione di errore di cui preoccuparsi.

I primi ciclo viene eseguito n volte, dove n è la profondità di node1, quindi è O (n). Il secondo ciclo viene eseguito volte m, dove m nella profondità del nodo 2. La ricerca nella lista temp è (nel peggiore dei casi) n. Così il secondo ciclo è O (m * n), e domina, per cui la funzione viene eseguito in O (m * n).

Se si utilizza una struttura di dati buon set (ad esempio, una tabella hash) per lo spazio temporaneo, invece di una lista, è possibile tagliare la ricerca a (in genere) O (1), senza aumentare il costo di eventuali nodi temp. Questo riduce il nostro tempo per la funzione O (m).

Il requisito di spazio è O (n) in entrambi i casi.

Dal momento che non sappiamo nem prima del tempo, mettiamola in termini di numero totale di nodi dell'albero: S. Se l'albero è bilanciato, allora n ed m sono ciascuna delimitata da log_2 (S), in modo da il tempo di esecuzione è O (log_2 (S) ^ 2). Log_2 è abbastanza potente, quindi S avrebbe dovuto ottenere abbastanza grande prima avevo preoccupo per la potenza di 2. Se l'albero non è equilibrata, poi perdiamo il log_2 (l'albero potrebbe effettivamente degenerare in una lista collegata). Così il caso peggiore assoluto (quando un nodo è la radice e l'altra è la foglia di un albero completamente degenere) è O (S ^ 2).

Risposto il 30/08/2011 a 18:15
fonte dall'utente

voti
6

Impostare un puntatore a entrambi i nodi casuali. Trovare la profondità di ogni nodo attraversando all'inizio e contare la distanza dal nodo radice. Quindi impostare il puntatore a entrambi i nodi di nuovo. Per il nodo più profondo, attraversare fino entrambi i puntatori sono alla stessa profondità. Poi attraversare per entrambi nodi finché i puntatori puntano allo stesso nodo. Questo è il nodo predecessore.

Con "traverse up" Volevo solo dire spostare il puntatore al genitore del nodo corrente.

Modifica per chiarire: L'idea chiave è che quando entrambi i nodi sono alla stessa profondità, è possibile trovare il genitore comune molto velocemente solo per semplice attraversamento. Quindi si sale quello inferiore fino a quando entrambi sono alla stessa profondità, e poi si attraversa su. Mi dispiace non so davvero C o che avrei scritto il codice, ma che algoritmo dovrebbe rispondere alla tua domanda.

Modifica di nuovo: E il mio metodo viene eseguito in O (log n) () tempo e O (1) di memoria.

Un'altra modifica: O (log (n)) in un albero bilanciato. Le prestazioni nel caso peggiore è O (n) per un albero sbilanciato. grazie @DaveCahill

Risposto il 30/08/2011 a 20:15
fonte dall'utente

voti
1

Questo problema è stato molto ben studiato e ci sono algoritmi noti che possono risolvere in tempo lineare. Questo documento descrive molti approcci diversi che è possibile utilizzare per risolverlo. Admittedtly si tratta di un documento di ricerca e quindi gli algoritmi sono un po 'complicato, ma alcuni degli approcci che descrive sono in realtà abbastanza fattibile.

Risposto il 30/08/2011 a 20:47
fonte dall'utente

voti
7

Forse approccio sciocco:

Generare il percorso da ciascun nodo alla radice, memorizzazione come una stringa di "L" e "R".

Invertire queste stringhe. Prendete il più lungo prefisso comune - ora avete il percorso per l'antenato comune.

Risposto il 30/08/2011 a 22:21
fonte dall'utente

voti
0
  1. Pre traversal ordine a meno che un 1 del nodo è soddisfatta e salvare i nodi visitati uptil ora.

  2. Ordine simmetrico, comincerà a salvare i nodi quando un 1 (di due nodi forniti) nodo è soddisfatta, e salvare l'elenco fino al nodo successivo è soddisfatta.

  3. inviare ordine di attraversamento, iniziare a risparmiare i nodi quando entrambi i nodi hav stato visitato ...
               UN         
      AVANTI CRISTO         
  DEFG       
HIJKLMNO     

Supponiamo H ed E sono due nodi casuali.

  1. ABDH
  2. HDIBJE
  3. EBLMENOGCA

Trova il primo nodo comune a tutti e tre ...

Risposto il 15/01/2012 a 15:55
fonte dall'utente

voti
3

Penso che si può solo fare una ricerca contemporaneamente per entrambi i nodi; il punto in cui la ricerca diverge è l'antenato comune.

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

È interessante notare che questo approccio scalare a più di due nodi (controllare tutti loro di essere sul lato sinistro tree, etc.)

Risposto il 05/02/2012 a 06:18
fonte dall'utente

voti
0

hi questo tornerà più basso valore del nodo antenato dove radice di albero e val1, val2 - vengono passati valori> di dati per i nodi

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;      
        }
}
Risposto il 25/09/2012 a 11:57
fonte dall'utente

voti
0

Qui ci sono due approcci in C # (.net) (entrambi discussi sopra) per riferimento:

  1. Versione ricorsiva di trovare LCA in albero binario (O (N) - al più ciascun nodo viene visitato) (punti principali della soluzione è LCA è (a) solo nodo albero binario in cui entrambi gli elementi trovano ai lati dei sottoalberi (sinistra e destra) è LCA. (b) e inoltre non importa quale nodo è presente entrambi i lati - inizialmente ho cercato di mantenere queste informazioni, e, ovviamente, la funzione ricorsiva diventare così confuso una volta ho capito che, è diventato molto elegante..

  2. Cercando entrambi i nodi (O (n)), e tenere traccia dei percorsi (usa spazio extra - così, # 1 è probabilmente superiore anche pensato che lo spazio è probabilmente irrilevante se l'albero binario è ben equilibrato come allora il consumo di memoria in più sarà solo in O (log (N)).

    in modo che i percorsi sono confrontati (essentailly simile alla risposta accettato - ma i percorsi è calcolato assumendo nodo puntatore non è presente nel nodo albero binario)

  3. Solo per il completamento ( non relative a mettere in discussione ), LCA in BST (O (log (N))

  4. test

Ricorsivo:

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

dove sopra la versione ricorsiva privato viene richiamato dal seguente metodo pubblico:

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

Soluzione tenendo traccia dei percorsi di entrambi i nodi:

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

dove FindNodeAndPath è definito come

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) - non collegata (solo per il completamento per riferimento)

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

unit test

[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);
            }
        }
Risposto il 14/07/2014 a 14:02
fonte dall'utente

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