Das Finden der gemeinsamen Vorfahren in einem binären Baum

stimmen
7

Diese Frage wurde mir in einem Interview gefragt: Ich habe einen binären Baum, und ich habe, um den gemeinsamen Vorfahren zu finden (Eltern) zwei Zufalls Knoten dieses Baumes gegeben. Ich bin auch ein Zeiger auf den Wurzelknoten gegeben.


Meine Antwort ist:

Traverse den Baum getrennt für beide Knoten, bis Sie den Knoten erreichen, die erwartet wird. Parallel während dem Verfahren Laden, um das Element und die nächste Adresse in einer verknüpften Liste. Dann haben wir zwei verkettete Listen mit uns. So versuchen die beiden verknüpften Listen und den letzten gemeinsamen Knoten sowohl in der verknüpften Listen Vergleich der Eltern ist.

Ich denke, dass diese Lösung richtig ist, korrigieren Sie mich, wenn ich falsch bin. Wenn diese Lösung richtig ist, kann ich weiß, ist dies die einzige bessere Lösung für diese Aufgabe oder gibt es eine andere bessere Lösung als das!

Veröffentlicht am 30/05/2011 um 11:18
quelle vom benutzer
In anderen Sprachen...                            


10 antworten

stimmen
2

Machen Sie eine Ebene um Traversal, und für jeden Knoten, denen wir begegnen wir seine Kinder überprüfen. Wenn sie die bereitgestellten Zufallsknoten sind, dann ist der Vorfahr-Knoten gefunden.

EDIT1:

Hier eine Übersicht

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

AKTUALISIEREN

Der bisherige Algorithmus findet nur gemeinsam Eltern (direkte Vorfahre), also wenn zwei Knoten zufällig ausgewählt, wenn kein Kind von gemeinsamem Elternteil würde keine Antwort gefunden werden.

Der unten Algorithmus gemeinsame Vorfahren finden und nicht nur die Eltern.

Ich denke, der folgende Algorithmus funktioniert:

Machen Sie eine Nachordnungsdurchquerung des binären Baumes, und findet für den Zufallsknoten 1 r1, wenn wir sie markieren Sie es dann in einer Zustandsvariablen finden in seinen Zustand ein , und halten Sie für den zweiten Knoten zu finden, wenn dann die Zustandsvariablen aktualisieren gefunden Zustand zwei und stoppt immer zurückkehren zu suchen. Die Zustandsvariable sollte von jedem Knoten zu seinen Eltern (rekursiv) übergeben werden. Der erste Knoten, der die Zustandsgröße in antrifft Zustand zwei ist die gemeinsame Vorfahre.

Die Implementierung des Algorithmus ist wie folgt:

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

Ich denke , dies richtig funktioniert, obwohl ich immer noch bin der Algorithmus Richtigkeit zu beweisen. Es ist ein Nachteil, das ist, wenn ein Knoten ein Kind von einem anderen Knoten ist, dann wird es nur den Knoten zu drucken , die die Eltern des anderen ist, statt den Druck der Eltern von ihnen. Wenn einer der Zufallsknoten ein Vorfahre eines anderen Zufallsknoten ist dann anstelle des Druckens der Vorfahr Zufallsknoten, es wird das übergeordnete davon drucken. In dem Fall , in dem eine des Zufallsknoten der Wurzelknoten ist, wird es nichts zu drucken, wie es immer der Vorfahr des anderen Zufallsknoten ist, und daher ihre gemeinsamen Vorfahren existieren nicht. In diesem speziellen Fall wird die Funktion Rückkehr 0x03in mainund es kann festgestellt werden.

Da dieser Algorithmus daher eine Nachordnungsdurchquerung tut dies erfordert O (n) Ausführungszeit und damit O (n) Speicher. Auch als die Suche so schnell stoppt beide Knoten gefunden werden, desto geringer ist der Knoten desto schneller ist die Suche beendet.

AKTUALISIEREN

Hier sind einige der Modus Diskussionen: Wie die niedrigste gemeinsame Vorfahre von zwei Knoten in jedem binären Baum zu finden?

Beantwortet am 30/05/2011 um 11:23
quelle vom benutzer

stimmen
0

@Above, das wird nicht funktionieren, weil man davon aus, dass beide Knoten sofort Kind von einem bestimmten Knoten sind ...

            8
     10           12
 7             

und ich habe die Knoten als 7 und 12, müssen Antwort sein 8. Lässt wie folgt tun

    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;
     }
Beantwortet am 30/08/2011 um 17:29
quelle vom benutzer

stimmen
0

Pseudo-Code:

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

Wenn die Knoten definitiv Teil desselben Baumes sind, dann werden sie auf jeden Fall haben einen gemeinsamen Vorfahren (auch wenn es die Wurzel im schlimmsten Fall ist). So wird es immer beenden und es gibt keine Fehlerbedingung zu befürchten.

Die erste Schleife läuft n-mal, wobei n die Tiefe von node1 ist, so ist es O (n). Die zweite Schleife verläuft m-mal, wobei m in der Tiefe von node2. Das Nachschlagen in temporärer Liste ist (im schlimmsten Fall) n. So ist die zweite Schleife O (m * n), und es dominiert, so dass die Funktion läuft in O (m * n).

Wenn Sie eine guten Set-Datenstruktur (zB eine Hash-Tabelle) für den temporären Raum statt einer Liste verwenden, können Sie die Lookup (in der Regel) O (1) geschnitten, ohne die Kosten zu erhöhen Knoten zu Temp hinzufügen. Dies reduziert unsere Funktion Zeit O (m).

Der Platzbedarf ist O (n) oder so.

Da wir n die Zeit nicht wissen, und m voraus, macht sie es von der Gesamtzahl der Knoten im Baum in Bezug setzen: S. Wenn der Baum ausgeglichen ist, dann n und m ist jeweils begrenzt durch log_2 (S), so die Laufzeit ist O (log_2 (S) ^ 2). Log_2 ist ziemlich mächtig, so S ziemlich groß werden müßte, bevor mich über die Macht des 2. kümmern würde, wenn der Baum nicht ausgeglichen ist, dann verlieren wir die log_2 (der Baum tatsächlich in eine verknüpften Liste degenerieren könnte). So ist die absolute schlimmsten Fall (wenn ein Knoten die Wurzel ist und das andere ist das Blatt eines vollständig degenerierten Baum) O (S ^ 2).

Beantwortet am 30/08/2011 um 18:15
quelle vom benutzer

stimmen
6

Stellen Sie einen Zeiger auf beide der zufälligen Knoten. Findet die Tiefen jeden Knoten, der durch nach oben durchlaufen, und dem Abstand von dem Wurzelknoten zu zählen. Dann stellen Sie den Zeiger an beiden wieder Knoten. Für den tieferen Knoten durchqueren, bis beiden Zeiger in der gleichen Tiefe sind. Dann durchquert für beide Knoten, bis die Zeiger auf den gleichen Knoten zeigen. Das ist der Vorfahr-Knoten.

Mit „durchqueren bis“ Ich meine nur den Zeiger auf die Eltern des aktuellen Knotens bewegen.

Bearbeiten zu klären: Die Schlüsselidee ist , dass , wenn beide Knoten in der gleichen Tiefe sind, können Sie die gemeinsam Eltern finden sehr schnell nur durch einfache Traversal. So klettern Sie die unter beide bis zur gleichen Tiefe sind, und dann durchqueren Sie auf. Leider weiß ich nicht wirklich C oder würde ich Code schreiben, aber das Algorithmus sollte Ihre Frage beantworten.

Bearbeiten Sie wieder: Und meine Methode läuft in O (log (n)) Zeit und O (1) Speicher.

Ein weiterer edit: O (log (n)) in einem ausgeglichenen Baum. Worst-Case - Leistung ist O (n) für einen unausgeglichenen Baum. dank @DaveCahill

Beantwortet am 30/08/2011 um 20:15
quelle vom benutzer

stimmen
1

Dieses Problem ist sehr gut untersucht und es gibt bekannte Algorithmen , die es in der linearen Zeit lösen können. Dieses Papier beschreibt viele verschiedene Ansätze können Sie es lösen verwenden. Admittedtly ist es ein Forschungspapier und so sind die Algorithmen ein bisschen schwierig, aber einige der Ansätze beschreibt es sind eigentlich durchaus machbar.

Beantwortet am 30/08/2011 um 20:47
quelle vom benutzer

stimmen
7

Vielleicht dummer Ansatz:

Erzeugen, um den Pfad von jedem Knoten zu der Wurzel, ist es als eine Folge von „L“ zu speichern und „R“.

Umkehren diese Strings. Nehmen Sie den längsten gemeinsamen Präfix - Sie haben jetzt den Weg zum gemeinsamen Vorfahren.

Beantwortet am 30/08/2011 um 22:21
quelle vom benutzer

stimmen
0
  1. Pre Order Traversal, es sei denn jeder 1 des Knotens erfüllt ist, und speichern Sie die Knoten uptil jetzt besucht.

  2. Inorder Traversal, starten Sie den Knoten Speichern, wenn eine 1 (von den beiden vorgesehenen Knoten) Knoten erfüllt ist, und die Liste speichern, bis der nächste Knoten erfüllt ist.

  3. Beitrag Order Traversal, starten Sie den Knoten Speichern, wenn beide Knoten hav besucht ...
               EIN         
      BC         
  DEFG       
HIJKLMNO     

Angenommen, H und E sind zwei zufällige Knoten.

  1. ABDH
  2. HDIBJE
  3. EBLMENOGCA

Finden Sie den ersten Knoten gemeinsam in allen drei ...

Beantwortet am 15/01/2012 um 15:55
quelle vom benutzer

stimmen
3

Ich glaube, Sie könnten nur für beide Knoten gleichzeitig eine Suche; der Punkt, an dem die Suche abweicht ist der gemeinsame Vorfahr.

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

Interessanterweise würde dieser Ansatz zu mehr als zwei Knoten skalieren (Check für alle von ihnen auf der linken Seite sein tree, etc.)

Beantwortet am 05/02/2012 um 06:18
quelle vom benutzer

stimmen
0

Hallo dies niedrigsten Vorfahrenknotens Wert zurück, wo Wurzel des Baums und val1, val2 -> Datenwerte für Knoten übergeben werden, wobei

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;      
        }
}
Beantwortet am 25/09/2012 um 11:57
quelle vom benutzer

stimmen
0

Hier sind zwei Ansätze in C # (NET) (beide oben beschrieben) als Referenz:

  1. Rekursive Version LCA in Binärbaum (O (N) - als höchstens jeder Knoten besucht wird) das Finden (Hauptpunkte der Lösung ist LCA ist (a) nur Knoten im Binärbaum , wo beiden Elemente auf beiden Seiten der Teilbäume befinden ( die linken und rechts) ist LCA. (b) und auch spielt es keine Rolle , welcher Knoten vorhanden ist , auf beiden Seiten - zunächst habe ich versucht , dass Informationen zu halten, und natürlich die rekursive Funktion wurde so verwirrend , wenn ich es realisierte, es wurde sehr elegant..

  2. Such beide Knoten (O (N)), und die Verfolgung der Wege (verwendet zusätzlichen Platz - so, # 1 sogar wahrscheinlich überlegen ist gedacht der Raum wahrscheinlich vernachlässigbar ist, wenn der binäre Baum als dann zusätzlicher Speicherverbrauch wird gut ausgewogen ist nur in O (log (N)).

    so dass die Pfade werden verglichen (essentailly ähnlich wie akzeptierte Antwort - aber die Wege berechnet, indem Zeiger Knoten unter der Annahme, nicht vorhanden in dem Binärbaum Knoten)

  3. Gerade für die Fertigstellung ( nicht in Frage Ähnliche ), LCA in BST (O (log (N))

  4. Tests

rekursive:

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

wo über private rekursive Version wird durch folgende öffentliche Methode aufgerufen:

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

Lösung durch Spur von Wegen beiden Knoten zu halten:

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

wo FindNodeAndPath definiert ist 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) - nicht im Zusammenhang (nur für den Abschluss als Referenz)

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);
            }
        }
Beantwortet am 14/07/2014 um 14:02
quelle vom benutzer

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