Nalezení společného předka v binárním stromu

hlasů
7

Tato otázka byla požádána, aby mi v rozhovoru: Mám binární strom a musím najít společného předka (rodiče) dal dvě náhodné uzly tohoto stromu. Jsem také vzhledem k tomu, ukazatel na kořenový uzel.


Moje odpověď zní:

Procházet stromu zvlášť pro oba uzly, až se dostanete na uzel, který se očekává. Rovnoběžné, zatímco prochází skladujte prvek a další adresy do propojeného seznamu. Pak máme dvě provázané seznamy s námi. Takže zkuste si porovnat dvě provázané seznamy a poslední společný uzel v obou propojených seznamech je rodič.

Mám na mysli, že toto řešení je správné, opravte mě, jestli se mýlím. Pokud toto řešení je správné, mohu vědět, je to jen lepší řešení pro tento úkol nebo je nějaké jiné lepší řešení než to!

Položena 30/05/2011 v 11:18
zdroj uživatelem
V jiných jazycích...                            


10 odpovědí

hlasů
2

Udělejte si level objednávky traversal, a pro každý uzel se setkáme se podíváme na jeho děti. Jsou-li poskytnuté náhodné uzly, potom bylo zjištěno, že předek uzlu.

Edit1:

Zde je nástin

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

AKTUALIZACE

Předchozí algoritmus bude jen najít společné rodiče (přímý předek), tedy pokud dva náhodně vybrané uzly nejsou-li dítě ze společného rodiče žádná odpověď by se našlo.

Níže uvedený algoritmus najít společné předky a nejen rodiče.

Myslím, že následující algoritmus bude fungovat:

Udělejte si postorder průchod binárním stromem, a najít pro náhodné uzlu 1 r1, pokud zjistíme, že pak ji označit za státní proměnné být ve stavu jedné a držet hledání na druhém uzlu, je-li nalezen pak aktualizovat stav proměnné stát dva a zastavení vyhledávání stále vracet. Proměnná stavu by měly být předány při každém uzlu na jeho rodičů (rekurzivně). První uzel, který narazí na stavu proměnných ve stavu dvou je společný předek.

Implementace algoritmu je následující:

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

Myslím, že to bude fungovat správně, i když jsem ještě dokázat správnost algoritmu je. Existuje jedna nevýhoda, což znamená, že pokud jeden uzel je potomkem jiného uzlu, pak to bude tisknout pouze uzel, který je mateřskou společností druhého, místo tisku rodiče z nich. Jestliže jeden z náhodného uzlu je předek jiného náhodného uzlu pak namísto tisku předka náhodný uzel, vytiskne rodiče to. V případě, ve kterém jeden z náhodného uzlu je kořenový uzel, bude tisknout nic, protože je vždy předchůdce druhého náhodného uzlu, a proto je jejich společný předek neexistuje. V tomto zvláštním případě se funkce vrátí 0x03do main, a to může být detekována.

Vzhledem k tomu, algoritmus dělá postorder traverzování proto vyžaduje O (n) čas spuštění a tedy O (n) paměti. Také jako hledání se zastaví, jakmile oba uzly se nacházejí, tím menší uzly tím rychlejší hledání končí.

AKTUALIZACE

Zde jsou některé režimu diskuse: Jak najít nejnižší společný předek dvou uzlů v každém binárním stromem?

Odpovězeno 30/05/2011 v 11:23
zdroj uživatelem

hlasů
0

@Above, že to nebude fungovat, protože se předpokládá, že oba uzly jsou okamžité dítě nějakým konkrétním uzlu ...

            8
     10           12
 7             

A dal jsem uzlů jako 7 a 12, musí být odpověď 8. Umožňuje dělat takhle

    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;
     }
Odpovězeno 30/08/2011 v 17:29
zdroj uživatelem

hlasů
0

Pseudo kód:

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

V případě, že uzly jsou jednoznačně součástí téhož stromu, pak určitě mají společného předka (i když je to kořen v nejhorším případě). Takže to bude vždy ukončit a není chybový stav se obávat.

První smyčka běží n-krát, kde n je hloubka node1, takže je O (n). Druhá smyčka běží m-krát, přičemž M v hloubce NODE2. Vyhledávací do seznamu temp je (v nejhorším případě) n. Takže druhá smyčka je O (m * n), a to ovládá, takže funkce běží v O (m * n).

Používáte-li datovou strukturu dobrý set (např hash table) pro dočasné prostoru místo seznamu, můžete snížit na vyhledávání pro (obvykle) O (1), a to bez zvýšení nákladů na přidávání uzlů do temp. To snižuje naši funkci času na O (m).

Potřeba místa je O (n) v obou směrech.

Vzhledem k tomu, že nevíme n a m dopředu, řekněme to z hlediska celkového počtu uzlů ve stromu: S. Pokud je strom je vyvážená, pak n a m jsou vždy ohraničeny log_2 (S), takže doba běhu je O (log_2 (S) ^ 2). Log_2 je dost silný, takže S by měl dostat dost velký, než bych se starat o síle 2. V případě, že strom není vyvážená, pak ztratíme log_2 (strom ve skutečnosti může zvrhnout v propojeném seznamu). Takže absolutně nejhorší případ (když jeden uzel je kořen a druhá je list zcela degenerované stromu) je O (S ^ 2).

Odpovězeno 30/08/2011 v 18:15
zdroj uživatelem

hlasů
6

Nastavte ukazatel na obou náhodných uzlů. Najít hloubku každý uzel pojížděním do horní části a počítání vzdálenost od kořenového uzlu. Pak znovu nastavit ukazatel na obou uzlech. Pro hlubší uzlu přejít až do oba ukazatele jsou ve stejné hloubce. Pak přejít až k oba uzly, dokud ukazatele poukazují na stejném uzlu. To je předek uzlu.

Pod pojmem „přejít do“ Chci jen říct přesunout ukazatel na rodiče aktuálního uzlu.

Editovat objasnit: Klíčovou myšlenkou je, že když jsou oba uzly ve stejné hloubce, můžete najít společného předka velmi rychle pouhým jednoduchým průchod. Takže vy vyšplhat ten nižší, dokud oba jsou ve stejné hloubce, a pak přejít nahoru. Promiň, že jsem opravdu nevím, C, jinak bych psát kód, ale algoritmus by měl odpovědět na vaši otázku.

Editovat znovu: A moje metoda běží v O (log (n)) času a O (1) paměti.

Další úpravy: O (log (n)), ve vyváženém stromu. Nejhorší výkon je O (n) pro nevyváženou stromu. Díky @DaveCahill

Odpovězeno 30/08/2011 v 20:15
zdroj uživatelem

hlasů
1

Tento problém byl velmi dobře studoval a jsou známy algoritmy, které to může vyřešit v lineárním čase. Tento článek popisuje mnoho různých přístupů, které můžete použít k jeho řešení. Admittedtly je papír výzkumu a tak algoritmy jsou trochu složitější, ale některé z přístupů, které popisuje, jsou vlastně docela možné.

Odpovězeno 30/08/2011 v 20:47
zdroj uživatelem

hlasů
7

Možná hloupá přístup:

Generování cestu z každého uzlu ke kořenu, ukládání jako řetězec „L“ a „R“.

tyto řetězce zvrátit. Take nejdelší společný prefix - nyní máte cestu do společného předka.

Odpovězeno 30/08/2011 v 22:21
zdroj uživatelem

hlasů
0
  1. Předem objednat traversal, pokud jakékoliv 1 uzlu je splněna a ušetřete uzly navštívil uptil teď.

  2. Nezbytného průchod, začít šetřit uzly když některý 1 (ze dvou dodaných uzly) je splněna uzel, a uložit v seznamu, dokud je splněna další uzel.

  3. odesílat objednávky traversal, začít šetřit uzly, když hav oba uzly byly navštívili ...
               A         
      před naším letopočtem         
  DEFG       
HIJKLMNO     

Předpokládejme, H a E jsou dvě náhodné uzly.

  1. ABDH
  2. HDIBJE
  3. EBLMENOGCA

Najít první uzel běžné ve všech třech ...

Odpovězeno 15/01/2012 v 15:55
zdroj uživatelem

hlasů
3

Myslím, že bys mohl udělat vyhledávání současně pro oba uzly; bod, ve kterém hledání rozchází je společný předek.

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

Zajímavé je tento přístup by škálovat až na více než dvěma uzly (zkontrolovat všechny z nich, že je na levé straně tree, atd.)

Odpovězeno 05/02/2012 v 06:18
zdroj uživatelem

hlasů
0

hi to vrátí nejnižší hodnotu uzlu předek, kde kořen stromu a hodnota1, val2 -> data-hodnoty pro uzly jsou předávány

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;      
        }
}
Odpovězeno 25/09/2012 v 11:57
zdroj uživatelem

hlasů
0

Zde jsou dva přístupy c # (NET) (jak je popsáno výše) za použití:

  1. Rekurzivní verze nalezení LCA v binárním stromu (O (N) - jak je nanejvýš navštíví každý uzel) (hlavní body řešení je LCA (a) pouze uzel v binárním stromu, kde oba tyto prvky jsou umístěny po obou stranách na podstromy (levá a vpravo) je LCA. (b) a také, že nezáleží na tom, který uzel je přítomen na každé straně - zpočátku jsem se snažil udržet, že informace, a samozřejmě rekurzivní funkce natolik matoucí, když jsem si to uvědomil, to se stalo velmi elegantní..

  2. Vyhledávání oba uzly (O (n)), a sledování cesty (používá více prostoru - tak, # 1 je pravděpodobně lepší dokonce myslel, že prostor je pravděpodobně zanedbatelná v případě, že binární strom je dobře vyvážená, pak další spotřeba paměti bude jen v O (log (N)).

    tak, že cesty se porovnávají (essentailly podobné přijaté odpovědi - ale cesty se vypočte za předpokladu, že ukazatel uzel není v binárním stromu uzlu)

  3. Jen pro dokončení ( nesouvisí se ptát ), LCA v BST (O (log (N))

  4. zkoušky

rekurzivní:

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

kde výše soukromého rekurzivní verze je vyvolána následující veřejné metody:

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

Řešení pomocí sledování drah obou uzlech:

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

kde FindNodeAndPath je definována jako

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) - nesouvisí (jen pro splnění rozhodnutí)

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

testy jednotkových

[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);
            }
        }
Odpovězeno 14/07/2014 v 14:02
zdroj uživatelem

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