Как выполнить обход BST по порядку без рекурсии или стека, но с использованием родительских указателей?

Можно ли выполнить итеративный обход по порядку на BST, узел которого имеет родительский указатель (родитель корня — null) без использования флага visited или stack?

Я гуглил и не нашел ответа. Дело в том, как я могу знать - в определенном узле - что я только что пришел к нему, а не закончил все под ним?


person OmarOthman    schedule 29.04.2012    source источник
comment
Рекурсия? Хотя это косвенное использование стеков.   -  person Shubham    schedule 29.04.2012
comment
Это звучит как один из тех глупых вопросов интервью. Рекурсия, скорее всего, является ожидаемым ответом.   -  person pablochan    schedule 29.04.2012
comment
[@Shubham, @pablochan] Если вы прочитаете вопрос еще раз, вы обнаружите, что слово итеративный написано явно.   -  person OmarOthman    schedule 29.04.2012
comment
Ну тогда ответ нет (если только вы не можете где-то сохранить посещенные узлы)   -  person pablochan    schedule 29.04.2012
comment
@pablochan, ты уверен? Я думаю, вы можете это сделать, смотрите мой ответ.   -  person svick    schedule 29.04.2012
comment
@svick: я оставил комментарий до того, как ты ответил. Видимо я ошибался.   -  person pablochan    schedule 29.04.2012


Ответы (8)


Вы можете сделать это, вам просто нужно помнить последний посещенный узел вместе с текущим узлом. Это не запрещено условием задачи: и флаг visited на каждом узле, и флаг stack равны (в худшем случае) O(n), если вспомнить, что последний узел просто < em>О(1).

В C# алгоритм может выглядеть так:

static void Walk(Node node)
{
    Node lastNode = null;
    while (node != null)
    {
        if (lastNode == node.Parent)
        {
            if (node.Left != null)
            {
                lastNode = node;
                node = node.Left;
                continue;
            }
            else
                lastNode = null;
        }
        if (lastNode == node.Left)
        {
            Output(node);

            if (node.Right != null)
            {
                lastNode = node;
                node = node.Right;
                continue;
            }
            else
                lastNode = null;
        }
        if (lastNode == node.Right)
        {
            lastNode = node;
            node = node.Parent;
        }
    }
}
person svick    schedule 29.04.2012
comment
Этот код находится в бесконечном цикле. Когда он пытается посетить лист, после того, как он подходит с левой стороны, последний узел будет нулевым, а когда он намеревается пройти вниз с правой стороны, он войдет в ветвь (последний == node.left), и сделать то же самое снова и снова. - person Wilhelm; 20.02.2014
comment
@ Вильгельм Нет, не будет. Обратите внимание, что правая ветвь находится в if, а не else if. Итак, Поднявшись с левой стороны (в lastNode == node.Left ветке), сразу смотрит на правую сторону (lastNode == node.Right) и только после этого зацикливается. - person svick; 20.02.2014
comment
Итак, вам нужен родительский указатель, нельзя ли поступить иначе? - person bneil; 04.07.2014
comment
@bneil, вы также можете использовать резьбовое дерево. Если вы хотите избежать стека и родительских указателей. - person Johan; 22.12.2015

Вот еще один способ сделать это. Я думаю, что это по сути эквивалентно ответу Свика, но избегает дополнительной переменной. Эта версия реализована на Python:

node=root
if node is not None:
  while node.left is not None:
    node=node.left
  while node is not None:
    output(node)
    if node.right is not None:
      node=node.right
      while node.left is not None:
        node=node.left
    else:
      while node.parent is not None and node.parent.right is node:
        node=node.parent
      node=node.parent

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

person Vaughn Cato    schedule 29.04.2012
comment
Код, который предоставил Вон Катон, действительно обходит дерево по порядку, но, насколько я могу судить, он запускается в бесконечном цикле, как только достигает конца дерева. Кто-нибудь знает способ исправить это? - person ; 26.06.2012
comment
@Nathan: я не вижу бесконечного цикла, о котором ты говоришь. Когда он проходит к самому правому узлу, последний цикл должен отправить его обратно в корень и, наконец, установить узел в родителя корня, который должен быть None. Затем внешний цикл while завершится. - person Vaughn Cato; 26.06.2012
comment
Хорошая и чистая реализация +1 - person Johan; 22.12.2015
comment
Протестировано двоичное дерево как root = 2, left = 1 и right = 3. Оно не будет работать и выводит только левый узел. - person Patrick; 13.07.2018
comment
@Patrick: я не смог воспроизвести эту проблему. Кажется, все работает нормально: codepad.org/njxp5tba - person Vaughn Cato; 13.07.2018
comment
@VaughnCato Спасибо за ответ. Я мог ошибиться прошлой ночью. - person Patrick; 14.07.2018

Используя правильную идею svick (см. его ответ), это протестированный код на C++ . Обратите внимание, что я не тестировал его код и даже не смотрел на него, я просто взял его идею и реализовал свою собственную функцию.

void in_order_traversal_iterative_with_parent(node* root) {
node* current = root;
node* previous = NULL;

while (current) {
    if (previous == current->parent) { // Traversing down the tree.
        previous = current;
        if (current->left) {
            current = current->left;
        } else {
            cout << ' ' << current->data;
            if (current->right)
                current = current->right;
            else
                current = current->parent;
        }
    } else if (previous == current->left) { // Traversing up the tree from the left.
        previous = current;
        cout << ' ' << current->data;
        if (current->right)
            current = current->right;
        else
            current = current->parent;
    } else if (previous == current->right) { // Traversing up the tree from the right.
        previous = current;
        current = current->parent;
    }
}

cout << endl;
}
person OmarOthman    schedule 30.04.2012

Мое решение Java без добавления какого-либо флага в существующее ДЕРЕВО. И нет родительского указателя. Этот подход будет удерживать узлы до высоты дерева. Пожалуйста, посмотрите.

https://github.com/skanagavelu/Algorithams/blob/master/src/tree/InOrderTraversalIterative.java

person Kanagavelu Sugumar    schedule 16.11.2015

Шаг 1: напишите функцию, которая возвращает преемника по порядку

Шаг 2. Начиная с крайнего левого узла, найдите преемника по порядку, пока не останется ни одного.

    public class TreeNode {
      int data;
      TreeNode left;
      TreeNode right;
      TreeNode parent;
    }

    public class TreeUtility {
      public void inorderNoRecursion(TreeNode root) {
        TreeNode current = leftmostNode(root);
        while(current != null) {
          System.out.println(current.data);
          current = inorderSuccessor(current);
        }
      }

      public TreeNode inorderSuccessor(TreeNode node) {
        if (node.right!=null) {
          return leftmostNode(node.right);
        }

        TreeNode p = node.parent;
        TreeNode c = node;
        while(p!=null && c != p.left) {
          c = p;
          p = p.parent;
        }
        return p;
      }

      private TreeNode leftmostNode(TreeNode node) {
        while (node.left != null) {
          node = node.left;
        }
        return node;
      }
    }
person rahul    schedule 06.02.2016

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

  1. Установите v в корень.
  2. Пока у v есть левый дочерний элемент, установите v в его левый дочерний элемент.
  3. Урожайность v.
  4. Если v является корнем, то возврат.
  5. Установите p в родительский элемент v.
  6. Если правым дочерним элементом p является v, установите v в p и перейдите к шагу 4.
  7. Урожайность с.
  8. Если у p есть правый дочерний элемент, установите v в правый дочерний элемент p и перейдите к шагу 2.
  9. Установите v на p и перейдите к шагу 4.
person oqi    schedule 29.04.2012
comment
Ваш код не проходит слишком много тестов. Я перевел его в точный спагетти-код на C++ и проверил на своем наборе тестов. - person OmarOthman; 30.04.2012

Это на С++:

void InOrder(Node *r)
{
   if(r==NULL)
         return;

   Node *t=r;

   while(t!=NULL)
       t=t->left;

  while(t!=r)
  {
     if(t==(t->parent->left))
     {
        cout<<t->parent->data;
        t=t->parent->right;
       if(t!=NULL)
      {
       while(t!=NULL)
          t=t->left;
      } 
      if(t==NULL)
          t=t->parent;
     }
     if(t==t->parent->right)
     {
        t=t->parent;
     }
  }
}
person Sasi    schedule 14.08.2012

person    schedule
comment
Ответы, содержащие только код без пояснений, не очень полезны. - person svick; 01.01.2014