Trouver l'ancêtre commun dans un arbre binaire

voix
7

Cette question m'a été posée dans une interview: J'ai un arbre binaire et je dois trouver l'ancêtre commun (parent) soit deux noeuds aléatoires de cet arbre. Je suis également donné un pointeur vers le nœud racine.


Ma réponse est:

Traverse l'arbre séparément pour les deux noeuds jusqu'à ce que vous atteignez le nœud qui est prévu. Parallèle en traversant magasin l'élément et l'adresse suivante dans une liste chaînée. Ensuite, nous avons deux listes chaînées avec nous. Alors, essayez de comparer les deux listes chaînées et le dernier noeud commun dans les deux listes chaînées est le parent.

Je pense que cette solution est correcte, corrigez-moi si je me trompe. Si cette solution est correcte, puis-je savoir est-ce la seule solution pour mieux cette tâche ou est-il une autre solution meilleure que cela!

Créé 30/05/2011 à 11:18
source utilisateur
Dans d'autres langues...                            


10 réponses

voix
2

Effectuer un ordre de niveau traversal, et pour chaque nœud que nous rencontrons, nous vérifions ses enfants. Si ce sont les nœuds aléatoires fournis, le noeud ancêtre se trouve.

EDIT1:

Voici un aperçu

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

METTRE À JOUR

L'algorithme précédent ne trouve les parents communs (ancêtre direct), donc si deux noeuds sélectionnés au hasard ne sont pas si un enfant de parent commun sans réponse serait trouvée.

L'algorithme ci-dessous trouver des ancêtres communs et non seulement les parents.

Je pense que l'algorithme suivant fonctionnera:

Faire un traversal postfixe de l'arbre binaire, et trouver pour le nœud aléatoire 1 r1, si nous trouvons marquer alors dans une variable d'état en état un , et continuer à trouver pour le deuxième noeud, si trouvé puis mettez à jour la variable d'état état de deux , et arrêter la recherche de plus en retour. La variable d'état doit être adoptée par chaque nœud à ses parents (récursive). Le premier nœud qui rencontre la variable d'état dans un état à deux est l'ancêtre commun.

La mise en œuvre de l'algorithme est la suivante:

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

Je pense que cela fonctionne correctement, mais je suis toujours à prouver l'exactitude de l'algorithme. Il y a un inconvénient, ce qui est, si un nœud est un enfant d'un autre nœud, puis il imprime uniquement le nœud qui est le parent de l'autre, au lieu d'imprimer le parent d'entre eux. Si l' un des nœud aléatoire est un ancêtre d'un autre nœud aléatoire puis au lieu d'imprimer l'ancêtre noeud aléatoire, il imprime le parent de celui - ci. Dans le cas où l' un des nœud aléatoire est le nœud racine, il imprime rien, car il est toujours l'ancêtre de l'autre nœud aléatoire, et donc leur ancêtre commun n'existe pas. Dans ce cas particulier la fonction sera de retour 0x03dans mainet il peut être détecté.

Comme cet algorithme fait un traversal postorder donc il faut O (n) temps d'exécution et donc O (n) de mémoire. De plus que la recherche s'arrête à la fois les nœuds se trouvent, le moins profond des nœuds plus vite la recherche se termine.

METTRE À JOUR

Voici quelques discussions de mode: Comment trouver le plus petit ancêtre commun de deux noeuds dans un arbre binaire?

Créé 30/05/2011 à 11:23
source utilisateur

voix
0

@Above, cela ne fonctionnera pas, parce que vous assumez que les deux nœuds sont adaptés aux enfants immédiat de un nœud particulier ...

            8
     10           12
 7             

et j'ai donné les noeuds que 7 et 12, la réponse doit être 8. permet de faire comme ceci

    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;
     }
Créé 30/08/2011 à 17:29
source utilisateur

voix
0

pseudocode:

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 les nœuds sont certainement partie du même arbre, ils auront certainement un ancêtre commun (même si elle est la racine dans le pire des cas). Ainsi, il sera toujours fin et il n'y a aucune condition d'erreur à craindre.

Les premiers essais n fois la boucle, où n est la profondeur de nœud1, il est donc O (n). La deuxième boucle court m fois, où m dans la profondeur de nœud2. La recherche dans la liste de température est (au pire) n. Ainsi, la seconde boucle est O (m * n), et elle domine, de sorte que la fonction exécute en O (m * n).

Si vous utilisez un bon jeu structure de données (par exemple, une table de hachage) pour l'espace temporaire au lieu d'une liste, vous pouvez couper la recherche à (généralement) O (1), sans augmenter le coût de l'ajout de nœuds à température. Cela réduit notre temps de fonction O (m).

L'espace requis est O (n) de toute façon.

Puisque nous ne savons pas n et m à l'avance, nous allons le mettre en termes du nombre total de noeuds dans l'arbre: S. Si l'arbre est équilibré, alors n et m sont délimitées chacune par log_2 (S), donc le temps d'exécution est O (log_2 (S) ^ 2). Log_2 est assez puissant, si S devrait obtenir assez grand avant que je me inquiète de la puissance de 2. Si l'arbre est pas équilibré, alors nous perdons la log_2 (l'arbre pourrait en fait dégénérer en une liste chaînée). Donc, le pire des cas absolu (quand un nœud est la racine et l'autre est la feuille d'un arbre complètement dégénéré) est O (S ^ 2).

Créé 30/08/2011 à 18:15
source utilisateur

voix
6

Définir un pointeur à la fois des noeuds aléatoires. Trouver la profondeur de chaque noeud, en traversant vers le haut et le comptage de la distance à partir du nœud racine. Réglez ensuite le pointeur sur les deux nœuds à nouveau. Pour le nœud plus profond, parcourir jusqu'à deux pointeurs sont à la même profondeur. Puis parcourir pour les deux noeuds jusqu'à ce que les pointeurs pointent vers le même noeud. Tel est le nœud ancêtre.

Par « TRAVERSE up » Je veux dire il suffit de déplacer le pointeur vers le parent du noeud courant.

Modifier pour clarifier: L'idée principale est que lorsque les deux noeuds sont à la même profondeur, vous pouvez trouver le parent commun très rapidement juste par traversal simple. Alors vous montez le bas jusqu'à ce que l' un deux sont à la même profondeur, puis vous traversez vers le haut. Désolé , je ne sais pas vraiment C ou j'écrire du code, mais cet algorithme devrait répondre à votre question.

Modifier à nouveau: Et ma méthode fonctionne en O (log (n)) et O (1) la mémoire.

Une autre modification: O (log (n)) dans un arbre équilibré. Pire performance cas est O (n) pour un arbre déséquilibré. Merci @DaveCahill

Créé 30/08/2011 à 20:15
source utilisateur

voix
1

Ce problème a été des algorithmes très bien étudié et il y a connus qui peuvent le résoudre dans le temps linéaire. Le présent document décrit de nombreuses approches différentes que vous pouvez utiliser pour le résoudre. Admittedtly est un document de recherche et donc les algorithmes sont un peu délicat, mais certaines des approches qu'il décrit sont en réalité tout à fait faisable.

Créé 30/08/2011 à 20:47
source utilisateur

voix
7

approche peut-être stupide:

Générer le chemin de chaque noeud de la racine, le stocker sous forme de chaîne de « L » et « R ».

Inverser ces chaînes. Prenez le plus long préfixe commun - vous avez maintenant le chemin de l'ancêtre commun.

Créé 30/08/2011 à 22:21
source utilisateur

voix
0
  1. En précommande traversal à moins que 1 du nœud est atteint et enregistrer les noeuds visités uptil maintenant.

  2. Afinde traversal, commencez à économiser les noeuds lorsqu'un 1 (des deux noeuds fournis) noeud est atteint, et enregistrer la liste jusqu'à ce que le nœud suivant est atteint.

  3. Publier traversal, commencez à économiser les nœuds lorsque les deux noeuds été visités ... hav
               UNE         
      avant JC         
  DEFG       
HIJKLMNO     

Supposons H et E sont deux noeuds aléatoires.

  1. ABDH
  2. HDIBJE
  3. EBLMENOGCA

Trouver le premier noeud commun dans les trois ...

Créé 15/01/2012 à 15:55
source utilisateur

voix
3

Je pense que vous pouvez simplement faire une recherche simultanément pour les deux noeuds; le point où la recherche est l'ancêtre divergeant commun.

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

Il est intéressant de cette approche à l' échelle de plus de deux noeuds (vérifier tous être sur le côté gauche de tree, etc.)

Créé 05/02/2012 à 06:18
source utilisateur

voix
0

salut ce retourne la valeur de noeud ancêtre le plus bas où la racine de l'arbre et val1, val2 -> des valeurs de données pour les noeuds sont transmises

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;      
        }
}
Créé 25/09/2012 à 11:57
source utilisateur

voix
0

Voici deux approches en c # (.net) (tous les deux discuté ci-dessus) pour référence:

  1. Version récursive de trouver LCA dans un arbre binaire (O (N) - au plus chaque noeud est visité) (points principaux de la solution est LCA est (a) seul noeud dans l' arbre binaire , où les deux éléments se trouvent de chaque côté des sous - arbres ( à gauche et à droite) est LCA. (b) et peu importe quel nœud est présent de chaque côté - au départ , j'ai essayé de garder cette information, et évidemment la fonction récursive devenir tellement confus une fois je l' ai réalisé, il est devenu très élégant..

  2. Recherche les deux noeuds (O (N)), et garder la trace des chemins (utilise l'espace supplémentaire - donc, # 1 est probablement supérieure trouvait même l'espace est probablement négligeable si l'arbre binaire est bien équilibré alors la consommation de mémoire supplémentaire sera juste O (log (N)).

    de sorte que les chemins sont comparés (essentailly similaire à réponse acceptée - mais les chemins est calculé en supposant noeud pointeur n'est pas présent dans le noeud d'arbre binaire)

  3. Juste pour l'achèvement ( non liée à la question ), LCA dans BST (O (log (N))

  4. Tests

récursive:

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

où la version ci-dessus récursive privée est invoquée en suivant la méthode publique:

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

Solution en gardant la trace des chemins des deux noeuds:

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

où FindNodeAndPath est définie comme

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 liée (juste pour l'achèvement de référence)

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

Tests unitaires

[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);
            }
        }
Créé 14/07/2014 à 14:02
source utilisateur

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