Нахождение общего предка в двоичном дереве

голоса
7

Этот вопрос был задан мне в интервью: У меня есть бинарное дерево, и я должен найти общий предок (родитель) дало два случайных узлов этого дерева. Я также дал указатель на корневой узел.


Мой ответ таков:

Траверса дерево отдельно для обоих узлов, пока вы дойдете до узла, который, как ожидается. Параллельно при обходе магазина элемента и следующий адрес в связанном списке. Тогда у нас есть две связанные списки с нами. Так что попробуйте сравнения двух связанных списков и последний общий узел в обеих связанных списках родителей.

Я имею в виду, что это решение является правильным, поправьте меня, если я ошибаюсь. Если это решение является правильным, может я знаю, это единственное лучшее решение для этой задачи, и есть ли другие лучшее решение, чем это!

Задан 30/05/2011 в 11:18
источник пользователем
На других языках...                            


10 ответов

голоса
2

Сделайте обход заказа уровня, и для каждого узла мы сталкиваемся мы проверяем свои ребенок. Если они являются случайными, предоставляемыми узлами, то узел предка найден.

EDIT1:

Вот план

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

ОБНОВИТЬ

Предыдущий алгоритм будет только найти общие родитель (прямой предок), поэтому, если два случайно выбранных узлы, если не ребенок общего родителя нет ответа не будет найден.

Ниже алгоритм поиска общих предков и не только родители.

Я думаю, что следующий алгоритм будет работать:

Сделайте postorder обход бинарного дерева, и найти для случайного узла 1 r1, если мы находим , то отметьте его в переменном состоянии , чтобы быть в состоянии одного , и продолжаем находить для второго узла, если найдено , то обновите переменное состояние состояние два , и прекратить поиски больше и вернуться. Переменная состояния должна быть передана каждым узлом к своим родителям (рекурсивно). Первый узел , который встречает переменные состояния в состоянии два является общим предком.

Реализация алгоритма выглядит следующим образом:

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

Я думаю , что это будет работать правильно, хотя я до сих пор доказать правильность алгоритма. Существует один недостаток, который, если один узел является потомком другого узла, то он будет печатать только узел , который является родителем другого, вместо того , чтобы печатать родитель из них. Если один из случайного узла является предком другого случайного узла , то вместо того , чтобы печатать предок случайный узел, он будет печатать родитель этого. В случае, когда один из случайного узла является корневым узлом, он не будет печатать ничего, как это всегда предок другого случайного узла, и , следовательно , их общий предок не существует. В этом особом случае функция будет возвращать 0x03в mainи он может быть обнаружен.

Поскольку этот алгоритм делает postorder обход поэтому требует O (N) времени выполнения и, следовательно, О (п) памяти. Кроме того, как поиск прекращается, как только и узлы найдены, мельче узлы, тем быстрее поиск заканчивается.

ОБНОВИТЬ

Вот некоторые обсуждения режима: Как найти наименьший общий предок двух узлов в любом двоичном дереве?

Ответил 30/05/2011 в 11:23
источник пользователем

голоса
0

@Above, это не будет работать, потому что предполагается, что оба узлом является непосредственным ребенком некоторого конкретного узла ...

            8
     10           12
 7             

и я дал узлы 7 и 12, ответ должен быть 8. Давайте делать так

    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;
     }
Ответил 30/08/2011 в 17:29
источник пользователем

голоса
0

псевдокод:

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

Если узлы, безусловно, часть того же самого дерева, то они, безусловно, имеют общего предка (даже если это корень в худшем случае). Так что это всегда будет прекращено, и нет состояния ошибки беспокоиться.

Первые проходит цикл п раз, где п глубина node1, так что это O (п). Второй цикл выполняется т раз, где М в глубине node2. Поиск в список Темпа является (в худшем случае) п. Таким образом, второй контур представляет собой О (т * п), и она доминирует, так что функция работает в O (M * N).

Если вы используете структуру данных, хороший набор (например, хэш-таблицу) для темп пространства вместо списка, вы можете сократить поиск до (обычно) O (1), не увеличивая стоимость добавления узлов темпа. Это сокращает наше время на функции O (м).

Необходимое пространство представляет собой О (п) в любом случае.

Так как мы не знаем, п и м впереди времени, давайте его с точки зрения общего числа узлов в дереве: S. Если дерево сбалансировано, то п и м каждый ограничен log_2 (S), так время работы представляет собой О (log_2 (S) ^ 2). Log_2 является довольно мощным, поэтому S придется получить довольно большой, прежде чем я бы беспокоиться о силе 2. Если дерево не сбалансировано, то мы теряем log_2 (дерево действительно может выродиться в связный список). Таким образом, абсолютный худший случай (когда один узел является корневым, а другой лист полностью вырожденного дерева) является O (S ^ 2).

Ответил 30/08/2011 в 18:15
источник пользователем

голоса
6

Установите указатель на обоих случайных узлов. Найти глубину каждого узла путем обхода к вершине и подсчета расстояния от корневого узла. Затем установите указатель на обоих узлах снова. Для более глубокого узла, пройти вверх до тех пор, оба указателя не находятся на одной и той же глубине. Затем траверс для обоих узлов, пока указатели не указывают на тот же узел. То есть узел предка.

Под «траверс вверх» Я просто имею в виду переместить указатель на родителя текущего узла.

Изменить , чтобы уточнить: Ключевая идея заключается в том , что , когда оба узла находятся на одной и той же глубине, вы можете найти общий родитель очень быстро , просто простым обходом. Таким образом , вы поднимаетесь нижний один , пока оба на той же глубине, а затем пройти вверх. К сожалению , я не знаю , C или я бы написать код, но алгоритм должен ответить на ваш вопрос.

Редактировать снова: И мой метод работает в O (журнал (п)) времени и O (1) памяти.

Другое редактирование: O (журнал (п)) в сбалансированном дереве. Производительность наихудшего случая представляет собой О (п) для несбалансированного дерева. Благодаря @DaveCahill

Ответил 30/08/2011 в 20:15
источник пользователем

голоса
1

Эта проблема была очень хорошо изучена и существует известные алгоритмы , которые могут решить ее в линейное время. В данной статье описаны различные подходы , которые можно использовать , чтобы решить эту проблему. Admittedtly это научно - исследовательская работа и поэтому алгоритмы немного сложнее, но некоторые из подходов , которые он описывает, на самом деле вполне реально.

Ответил 30/08/2011 в 20:47
источник пользователем

голоса
7

Может быть, глупый подход:

Генерировать путь от каждого узла к корню, сохраняя его в виде строки из «L» и «R».

Реверс этих строк. Возьмите самый длинный общий префикс - теперь у вас есть путь к общему предку.

Ответил 30/08/2011 в 22:21
источник пользователем

голоса
0
  1. Предварительный заказ обход, если какой-либо 1 узла выполняется и сохранить узлы посетили uptil Теперь.

  2. Симметричный обходе, начать экономить узлы, когда какие-либо один (из двух узлов, предоставляемых) узел выполняется, и сохраните список, пока следующий узел не будет выполнен.

  3. отправлять обход порядка, начать экономить узлы, когда ВГ посетили оба узлы ...
         
      До нашей эры         
  DEFG       
HIJKLMNO     

Пусть Н и Е являются двумя случайными узлами.

  1. ABDH
  2. HDIBJE
  3. EBLMENOGCA

Найти первый узел общего во всех трех ...

Ответил 15/01/2012 в 15:55
источник пользователем

голоса
3

Я думаю, вы могли бы просто сделать поиск одновременно для обоих узлов; точка, в которой поиск расходится является общим предком.

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

Интересно , этот подход будет масштабироваться до более чем двух узлов (проверить их все , чтобы быть на левой стороне tree, и т.д.)

Ответил 05/02/2012 в 06:18
источник пользователем

голоса
0

привет это будет возвращать наименьшее значение родительского узла, где корень дерева и знач1, знач2 - значения> данных для узлов, передаваемой

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;      
        }
}
Ответил 25/09/2012 в 11:57
источник пользователем

голоса
0

Вот два подхода в C # (.NET) (как описано выше) для справки:

  1. Рекурсивный вариант нахождения LCA в двоичном дереве (O (N) - как у наиболее посещается каждый узел) (основные точки раствора LCA является (а) только узел в бинарном дереве , где оба элемента располагаются по обе стороны от поддеревьев (слева и справа) LCA. (б) а также не имеет значения , какой узел присутствует обе стороны - сначала я пытался сохранить эту информацию, и , очевидно , рекурсивная функция стала настолько запутанной , когда я понял, он стал очень элегантно..

  2. Поиск оба узлов (O (N)), и отслеживание путей (использует дополнительное пространство - так, # 1, вероятно, превосходят даже думала, что пространство, вероятно, ничтожно, если бинарное дерево хорошо сбалансировано, как то дополнительное потребление памяти будет только в O (журнал (N)).

    так что пути сравниваются (essentailly аналогичного принятого ответа - но пути вычисляются в предположении, узла указателей нет в двоичном узле дерева)

  3. Только для завершения ( не связанные с вопросом ), LCA в BST (O (журнал (N))

  4. тесты

Рекурсивный:

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

где над частной рекурсивной версией вызываются следующим общедоступным методом:

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

Решение путем отслеживания путей обоих узлов:

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

где FindNodeAndPath определяется как

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) - не связаны между собой (только для завершения для справки)

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

модульные тесты

[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);
            }
        }
Ответил 14/07/2014 в 14:02
источник пользователем

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