Encontrar el ancestro común en un árbol binario

votos
7

Esta pregunta se hizo a mí en una entrevista: Tengo un árbol binario y tengo que encontrar el ancestro común (padre) dado dos nodos al azar de ese árbol. También me dieron un puntero al nodo raíz.


Mi respuesta es:

Recorrer el árbol separado, tanto para los nodos hasta llegar al nodo que se espera. Paralelo al atravesar el elemento de la tienda y la siguiente dirección en una lista enlazada. Entonces tenemos dos listas enlazadas con nosotros. Así que trate de comparar las dos listas enlazadas y el último nodo común tanto en las listas enlazadas es el padre.

Pienso que esta solución es correcta, me corrija si estoy equivocado. Si esta solución es correcta, se puede saber es ésta la única solución mejor para esta tarea o hay alguna otra solución mejor que esto!

Publicado el 30/05/2011 a las 11:18
fuente por usuario
En otros idiomas...                            


10 respuestas

votos
2

Hacer un recorrido en orden de nivel y para cada nodo que nos encontramos, comprobamos sus hijos. Si son los nodos aleatorios proporcionados, entonces el nodo antepasado se encuentra.

EDIT1:

A continuación se presenta un esquema

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

ACTUALIZAR

El algoritmo anterior sólo encontrará los padres comunes (antepasado directo), por lo tanto, si dos nodos seleccionados al azar, si no eres una criatura del padre común hay respuesta se encontraría.

El siguiente algoritmo encontrará ancestros comunes y no sólo los padres.

Creo que el siguiente algoritmo funcionará:

Hacer un recorrido en orden posterior del árbol binario, y encontrar para el nodo aleatorio 1 r1, si lo encontramos luego marcarlo en una variable de estado para estar en estado de uno , y seguimos encontrando para el segundo nodo, si se encuentra a continuación, actualizar la variable de estado de estado de dos , y detener la búsqueda y volver más. La variable de estado se debe pasar por cada nodo a sus padres (de forma recursiva). El primer nodo que se encuentra con la variable de estado en estado de dos es el antepasado común.

La implementación del algoritmo es el siguiente:

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

Creo que esto funcione correctamente, aunque todavía estoy para demostrar la corrección del algoritmo. Hay un inconveniente, que es, si un nodo es un hijo de otro nodo, a continuación, se imprime sólo el nodo que es el padre de la otra, en lugar de imprimir el padre de ellos. Si uno de los nodos al azar es un antepasado de otro nodo al azar a continuación, en lugar de imprimir el nodo al azar antepasado, se imprimirá el padre de ella. En el caso en el que uno de los nodos al azar es el nodo raíz, se imprimirá nada, ya que siempre es el ancestro del otro nodo al azar, y por lo tanto no existe su ancestro común. En este caso especial, la función devolverá 0x03en mainy puede ser detectado.

Como este algoritmo hace un recorrido postorden por lo tanto, requiere O (n) tiempo de ejecución y por lo tanto O (n) de memoria. Además, como la búsqueda se detiene tan pronto tanto los nodos se encuentran, el más superficial de los nodos extremos más rápida será la búsqueda.

ACTUALIZAR

Estas son algunas discusiones modo: ¿Cómo encontrar el ancestro común más bajo de los dos nodos en cualquier árbol binario?

Respondida el 30/05/2011 a las 11:23
fuente por usuario

votos
0

@Above, esto no va a funcionar, porque usted está asumiendo que ambos nodos son niños inmediata de algún nodo en particular ...

            8
     10           12
 7             

y di los nodos como 7 y 12, la respuesta debe ser 8. Permite hacer como esto

    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;
     }
Respondida el 30/08/2011 a las 17:29
fuente por usuario

votos
0

pseudocódigo:

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

Si los nodos son sin duda parte del mismo árbol, entonces definitivamente tendrán un ancestro común (incluso si es la raíz en el peor de los casos). Por lo que siempre se terminará y no hay ninguna condición de error que preocuparse.

Las primeras carreras de bucle N veces, donde n es la profundidad de node1, así que es O (n). El segundo bucle se ejecuta m veces, donde m en la profundidad de node2. Las operaciones de búsqueda en la lista temporal es (en el peor) n. Así que el segundo bucle es O (m * n), y domina, por lo que la función se ejecuta en O (m * n).

Si se utiliza una estructura de datos buen conjunto (por ejemplo, una tabla hash) para el espacio temporal en lugar de una lista, se puede cortar la búsqueda de (típicamente) O (1), sin aumentar el coste de añadir nodos a temp. Esto reduce el tiempo para la función O (m).

El requisito de espacio es O (n) de cualquier manera.

Dado que no sabemos N y M antes de tiempo, vamos a ponerlo en términos del número total de nodos en el árbol: S. Si el árbol está equilibrado, entonces n y m son cada uno delimitado por log_2 (S), por lo el tiempo de ejecución es O (log_2 (S) ^ 2). Log_2 es ​​bastante potente, por lo que S tendría que ser bastante grande antes de que yo me preocuparía por la potencia de 2. Si el árbol no es equilibrada, entonces se pierde la log_2 (el árbol realidad podría degenerar en una lista enlazada). Así que la absoluta peor de los casos (cuando un nodo es la raíz y la otra es la hoja de un árbol completamente degenerada) es O (S ^ 2).

Respondida el 30/08/2011 a las 18:15
fuente por usuario

votos
6

Establecer un puntero al tanto de los nodos al azar. Encuentra la profundidad de cada nodo mediante el desplazamiento hasta la parte superior y contar la distancia desde el nodo raíz. A continuación, establezca el puntero en ambos nodos de nuevo. Para el nodo más profundo, atravesar hasta ambos punteros son a la misma profundidad. Entonces atravesar para ambos nodos hasta que los punteros apuntan al mismo nodo. Ese es el nodo antecesor.

Por "Marcha hacia arriba" me refiero sólo mover el puntero al padre del nodo actual.

Editar para aclarar: La idea clave es que cuando ambos nodos están a la misma profundidad, se puede encontrar el padre común muy rápidamente simplemente por simple recorrido. Así se sube el inferior hasta que ambos se encuentran a la misma profundidad, y luego se recorre hacia arriba. Lo siento, no se sabe muy bien C o me gustaría escribir código, pero que el algoritmo debería responder a su pregunta.

Editar de nuevo: Y mi método se ejecuta en O (log (n)) y O (1) de memoria.

Otro edit: O (log (n)) en un árbol de equilibrado. El rendimiento del peor caso es O (n) para un árbol desequilibrado. gracias @DaveCahill

Respondida el 30/08/2011 a las 20:15
fuente por usuario

votos
1

Este problema se ha estudiado muy bien y hay algoritmos conocidos que pueden resolver en tiempo lineal. En este artículo se describe muchos enfoques diferentes que puede utilizar para resolverlo. Admittedtly se trata de un trabajo de investigación y así los algoritmos son un poco complicado, pero algunos de los enfoques que se describen son en realidad bastante factible.

Respondida el 30/08/2011 a las 20:47
fuente por usuario

votos
7

Tal enfoque tonta:

Generar la trayectoria de cada nodo de la raíz, almacenarla como una serie de "L" y "R".

Revertir estas cadenas. Tome el prefijo común más larga - ahora tiene la ruta de acceso al antepasado común.

Respondida el 30/08/2011 a las 22:21
fuente por usuario

votos
0
  1. orden transversal Pre menos que cualquier 1 del nodo se cumple y guardar los nodos visitados uptil ahora.

  2. Inorder traversal, comenzar a ahorrar los nodos cuando cualquier 1 (de los dos nodos proporcionados) se cumple nodo, y guardar la lista hasta que se cumpla el siguiente nodo.

  3. publicar transversal fin, empezar a ahorrar los nodos cuando hav sido visitado tanto los nodos ...
               UN         
      antes de Cristo         
  DEFG       
HIJKLMNO     

Supongamos H y E son dos nodos aleatorios.

  1. ABDH
  2. HDIBJE
  3. EBLMENOGCA

Encuentra el primer nodo común en los tres ...

Respondida el 15/01/2012 a las 15:55
fuente por usuario

votos
3

Creo que sólo podría hacer una búsqueda simultánea de ambos nodos; el punto en el que se aparta de la búsqueda es el antepasado común.

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

Es interesante que este enfoque podría escalar a más de dos nodos (comprobar todos ellos para estar en el lado izquierdo de tree, etc.)

Respondida el 05/02/2012 a las 06:18
fuente por usuario

votos
0

hola este volverá más bajo valor de nodo ancestro donde, val2 raíz del árbol y val1 - se están pasando los valores> de datos para los nodos

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;      
        }
}
Respondida el 25/09/2012 a las 11:57
fuente por usuario

votos
0

Aquí hay dos enfoques en c # (.NET) (ambos discutido anteriormente) para la referencia:

  1. Versión recursiva de la búsqueda de LCA en árbol binario (O (N) - como a lo sumo es visitado cada nodo) (puntos principales de la solución es LCA es (a) único nodo en el árbol binario en el que ambos elementos residen ambos lados de los subárboles (izquierda y la derecha) es LCA. (b) y también que no importa qué nodo está presente cada lado - al principio me trató de mantener esa información, y, obviamente, la función recursiva a ser tan confuso una vez que me di cuenta de que, se hizo muy elegante..

  2. Buscando ambos nodos (O (n)), y hacer el seguimiento de trayectorias (utiliza más espacio - así, # 1 es probablemente superior incluso pensó que el espacio es probablemente insignificante si el árbol binario está bien equilibrado como entonces el consumo de memoria adicional será sólo en O (log (N)).

    de modo que las trayectorias se comparan (essentailly similar a la respuesta aceptada - pero los caminos se calcula asumiendo nodo puntero no está presente en el nodo del árbol binario)

  3. Sólo por la realización ( no relacionado con la pregunta ), LCA en BST (O (log (n))

  4. pruebas

recursiva:

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

donde por encima de la versión recursiva privada se invoca siguiendo método público:

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

Solución mediante el seguimiento de trayectorias de ambos nodos:

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

donde FindNodeAndPath se define como

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) - no relacionado (sólo para la finalización de referencia)

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

Las pruebas unitarias

[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);
            }
        }
Respondida el 14/07/2014 a las 14:02
fuente por usuario

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