Het vinden van de gemeenschappelijke voorouder in een binaire boom

stemmen
7

Deze vraag werd gevraagd om me in een interview: Ik heb een binaire boom en ik moet de gemeenschappelijke voorouder (ouder) gegeven twee willekeurige knooppunten van die boom te vinden. Ik ben ook gegeven een verwijzing naar de root node.


Mijn antwoord is:

de boom doorkruisen afzonderlijk voor zowel de knooppunten tot u het knooppunt dat wordt verwacht te bereiken. Parallel terwijl het doorkruisen van winkel het element en het volgende adres in een gekoppelde lijst. Dan hebben we twee gekoppelde lijsten met ons. Dus probeer het vergelijken van de twee gekoppelde lijsten en de laatste gemeenschappelijke knooppunt in zowel de gelinkte lijsten wordt de ouder.

Ik denk dat deze oplossing juist is, corrigeer me als ik verkeerd ben. Als deze oplossing juist is, mag ik weten is dit de enige betere oplossing voor deze taak of is er een andere betere oplossing dan dit!

De vraag is gesteld op 30/05/2011 om 11:18
bron van user
In andere talen...                            


10 antwoorden

stemmen
2

Maak een niveau orde traversal, en voor elk knooppunt die we tegenkomen we controleren haar kinderen. Als ze de geleverde willekeurige knooppunten, dan is de voorouder knooppunt wordt gevonden.

EDIT1:

Hier is een overzicht

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

BIJWERKEN

De vorige algoritme vindt alleen gemeenschappelijke ouders (directe voorouder), dus als twee willekeurig geselecteerde knopen als niet een kind van gemeenschappelijke ouder geen antwoord zou worden gevonden.

Onderstaande algoritme gemeenschappelijke voorouders en niet alleen ouders vinden.

Ik denk dat de volgende algoritme zal werken:

Maak een postorder traversal van de binaire boom, en gevonden in de willekeurige knooppunt 1 r1, als we vinden het dan markeren in een toestand variabel te zijn in staat één en blijven vinden voor het tweede knooppunt, indien gevonden dan is het actualiseren van de toestandvariabele staat twee , en stoppen met zoeken meer en terug te keren. De staat variabele moet worden doorgegeven door elk knooppunt naar zijn ouders (recursief). Het eerste knooppunt dat de toestandvariabele in ontmoetingen toestand twee is de gemeenschappelijke voorouder.

De implementatie van het algoritme is als volgt:

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

Ik denk dat dit zal goed werken, maar ik ben nog steeds aan de juistheid van het algoritme te bewijzen. Er is een nadeel, dat, indien een knooppunt een kind van een ander knooppunt, dan zal alleen het knooppunt dat de ouder van het andere drukken, in plaats van het drukken de ouder ervan. Als een van de willekeurige knooppunt is een voorouder van een ander willekeurig knooppunt dan in plaats van het afdrukken van de voorouder willekeurige knooppunt, zal het de ouder van het af te drukken. In het geval dat een van de willekeurige knooppunt het hoofdknooppunt, zal niets af, omdat het altijd de voorouder van de andere willekeurige knooppunt en dus hun gemeenschappelijke voorouder bestaat niet. In dit speciale geval zal u de functie weer 0x03in mainen het kan worden gedetecteerd.

Aangezien dit algoritme doet derhalve een postorder traversal vereist O (n) uitvoeringstijd en dus O (n) geheugen. Ook het zoeken stopt zodra zowel de knooppunten worden gevonden, de ondiepere de knooppunten hoe sneller de zoektocht eindigt.

BIJWERKEN

Hier zijn een aantal mode discussies: Hoe de laagste gemeenschappelijke voorouder van twee knooppunten in een binaire boom te vinden?

antwoordde op 30/05/2011 om 11:23
bron van user

stemmen
0

@Above, dit zal niet werken, omdat je gaan ervan uit dat beide knooppunten zijn onmiddellijke kind van enkele bijzondere knooppunt ...

            8
     10           12
 7             

Ik gaf de knooppunten 7 en 12, moet antwoord 8. Laat als dit doen

    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;
     }
antwoordde op 30/08/2011 om 17:29
bron van user

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

Als de knooppunten zijn zeker deel uitmaken van dezelfde boom, dan zullen ze zeker hebben een gemeenschappelijke voorouder (ook al is het de wortel in het ergste geval). Zo zal het altijd te beëindigen en er is geen fout te maken over.

De eerste lus loopt n maal, waarbij n de diepte van node1, dus het is O (n). De tweede lus loopt m maal, waarbij m in de diepte van clustnode2. De lookup in temp lijst is (in het slechtste geval) n. Dus de tweede lus is O (m * n), en domineert, zodat de functie wordt uitgevoerd in O (m * n).

Als je een goede set datastructuur (bijvoorbeeld een hash tabel) te gebruiken voor de tijdelijke ruimte in plaats van een lijst, kunt u de lookup om (meestal) O snijden (1), zonder verhoging van de kosten van het toevoegen van knooppunten aan temp. Dit vermindert onze functie tijd O (m).

De benodigde ruimte is O (n) in beide richtingen.

Aangezien we niet n kennen en ben van tevoren, laten we het in termen van het totale aantal knooppunten in de boom: S. Als de boom evenwichtig, dan n en m zijn elk begrensd door log_2 (S), zodat de looptijd is O (log_2 (S) ^ 2). Log_2 is vrij krachtig, dus S zou moeten vrij groot te krijgen voordat ik zou zorgen te maken over de kracht van 2. Als de boom niet in balans is, dan verliezen we de log_2 (de boom eigenlijk zou kunnen ontaarden in een gekoppelde lijst). Zodat de absolute slechtste geval (als één knooppunt is de wortel en de andere is het blad van een volledig gedegenereerde boom) O (S ^ 2).

antwoordde op 30/08/2011 om 18:15
bron van user

stemmen
6

Stel een pointer op zowel van de willekeurige knooppunten. Vind de diepte van elk knooppunt door te verplaatsen naar de top en de afgelegde afstand van het hoofdknooppunt. Stel vervolgens de aanwijzer op beide knooppunten weer. Voor de diepere knooppunt doorlopen tot beide pointers op dezelfde diepte. Doorkruisen voor beide knooppunten totdat de wijzers wijzen naar hetzelfde knooppunt. Dat is de voorouder knooppunt.

Met "doorkruisen up" Ik bedoel gewoon verplaatst u de aanwijzer naar de ouder van het huidige knooppunt.

Bewerken om te verduidelijken: Het belangrijkste idee is dat wanneer beide nodes zijn op dezelfde diepte, kunt u de gemeenschappelijke ouder heel snel gewoon door eenvoudige traversal. Dus u de onderste totdat beide beklimmen op dezelfde diepte, en dan doorkruisen u op. Sorry dat ik weet niet echt C of ik zou code te schrijven, maar dat algoritme moet uw vraag te beantwoorden.

Bewerken weer: En mijn methode loopt in O (log (n)) tijd en O (1) geheugen.

Een ander Edit: O (log (n)) in een evenwichtige boom. Worst-case prestaties is O (n) voor een ongebalanceerde boom. Thanks @DaveCahill

antwoordde op 30/08/2011 om 20:15
bron van user

stemmen
1

Dit probleem is zeer goed bestudeerd en er zijn bekende algoritmen die het kan oplossen in lineaire tijd. Dit document beschrijft verschillende benaderingen die u kunt gebruiken om het op te lossen. Admittedtly het is een research paper en dus de algoritmen zijn een beetje lastig, maar sommige van de methoden die het beschrijft zijn eigenlijk heel haalbaar.

antwoordde op 30/08/2011 om 20:47
bron van user

stemmen
7

Misschien domme aanpak:

Genereren de weg van elk knooppunt naar de wortel, slaan als een reeks van "L" en "R".

Reverse deze snaren. Neem de langste gemeenschappelijke prefix - je hebt nu het pad naar de gemeenschappelijke voorouder.

antwoordde op 30/08/2011 om 22:21
bron van user

stemmen
0
  1. Pre order traversal, tenzij een 1 van het knooppunt wordt voldaan en sla de knooppunten bezocht uptil nu.

  2. Inorder traversal, beginnen met sparen de knooppunten wanneer een 1 (van de twee meegeleverde nodes) knoop wordt voldaan, en sla de lijst totdat het volgende knooppunt wordt voldaan.

  3. postorder traversal, beginnen met sparen de knooppunten wanneer zowel de knooppunten hav bezocht ...
               EEN         
      BC         
  DEFG       
HIJKLMNO     

Veronderstel H en E zijn twee willekeurige knooppunten.

  1. ABDH
  2. HDIBJE
  3. EBLMENOGCA

Vind het eerste knooppunt gebruikelijk in alle drie de ...

antwoordde op 15/01/2012 om 15:55
bron van user

stemmen
3

Ik denk dat je kon gewoon een zoekopdracht doen tegelijkertijd voor beide knooppunten; het punt waarop de zoektocht afwijkt is de gemeenschappelijke voorouder.

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

Interessant deze benadering schalen naar meer dan twee knooppunten (controleer allen worden aan de linkerkant van treeetc.)

antwoordde op 05/02/2012 om 06:18
bron van user

stemmen
0

> Gegevenswaarden te knopen worden doorgegeven - hi dit laagste voorouder nodewaarde indien wortel van de boom en val1, val2 terugkeren

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;      
        }
}
antwoordde op 25/09/2012 om 11:57
bron van user

stemmen
0

Hier zijn twee benaderingen in C # (NET) (beide hierboven besproken) voor referentie:

  1. Recursieve versie vinden LCA in binaire boom (O (N) - of ten hoogste elk knooppunt wordt bezocht) (hoofdpunten van de oplossing LCA (a) enkel knooppunt in binaire boom waarin beide elementen bevinden aan weerszijden van de deelbomen (linker en rechts) is LCA. (b) en ook het maakt niet uit welk knooppunt aanwezig weerszijden is - in eerste instantie heb ik geprobeerd om die info te houden, en uiteraard de recursieve functie worden zo verwarrend zodra ik besefte, het erg elegant werd..

  2. Zoeken beide knooppunten (O (N)), en het bijhouden van paden (maakt gebruik van extra ruimte - dus, # 1 is waarschijnlijk superieur zelfs dacht dat de ruimte is waarschijnlijk te verwaarlozen als de binaire boom is goed in balans en dan zal extra geheugengebruik net in O (log (N)).

    zodat de paden worden vergeleken (essentailly vergelijkbaar met geaccepteerde antwoord - maar de wegen wordt berekend onder aanname pointer knooppunt niet in de binaire boomstructuur)

  3. Voor de voltooiing ( niet gerelateerd aan vraag ), LCA in BST (O (log (N))

  4. Tests

recursieve:

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

waarboven private recursieve versie wordt aangevoerd door het volgen van openbare methode:

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

Oplossing door het bijhouden van de paden van beide knooppunten:

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

waarbij FindNodeAndPath wordt gedefinieerd als

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) - niet verwant (alleen voor voltooiing voor referentie)

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 Tests

[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);
            }
        }
antwoordde op 14/07/2014 om 14:02
bron van user

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