Перебор бинарного дерева с вспомогательным пространством O(1)

Можно ли выполнить итерацию по бинарному дереву в O(1) вспомогательном пространстве (без использования стека, очереди и т. д.), или это невозможно? Если это возможно, то как это можно сделать?

Изменить: ответы, которые я получил о том, что это возможно, если есть указатели на родительские узлы, интересны, и я не знал, что это можно сделать, но в зависимости от того, как вы на это смотрите, это может быть вспомогательным O (n) космос. Кроме того, в моем практическом случае нет указателей на родительские узлы. Отныне, пожалуйста, предполагайте это при ответе.


person dsimcha    schedule 26.04.2009    source источник
comment
Я рассматриваю ссылку на родителя как вспомогательное пространство O (n). Вы должны отредактировать свой вопрос, чтобы явно указать, что ваше двоичное дерево не имеет такого свойства.   -  person mmx    schedule 26.04.2009
comment
Как у вас может не быть указателей на ваши родительские узлы? как вы можете перебирать свое дерево вообще без них? это где стек входит? вам все равно понадобятся указатели родителей, чтобы выполнить балансировку.   -  person    schedule 22.05.2009


Ответы (11)


Боже, мне придется напечатать это из Кнута. Это решение принадлежит Джозефу М. Моррису [Inf. проц. Letters 9 (1979), 197–200]. Насколько я могу судить, он выполняется за время O(NlogN).

static void VisitInO1Memory (Node root, Action<Node> preorderVisitor) {
  Node parent = root ;
  Node right  = null ;
  Node curr ;
  while (parent != null) {
    curr = parent.left ;
    if (curr != null) {
      // search for thread
      while (curr != right && curr.right != null)
        curr = curr.right ;

      if (curr != right) {
        // insert thread
        assert curr.right == null ;
        curr.right = parent ;
        preorderVisitor (parent) ;
        parent = parent.left ;
        continue ;
      } else
        // remove thread, left subtree of P already traversed
        // this restores the node to original state
        curr.right = null ;
    } else
      preorderVisitor (parent) ;

    right  = parent ;
    parent = parent.right ;
  }
}

class Node
{
  public Node left  ;
  public Node right ;
}
person Anton Tykhyy    schedule 26.04.2009
comment
это разрушает дерево, не так ли? - person Brian R. Bondy; 27.04.2009
comment
Нет, он просто временно добавляет обратные ссылки из определенных листьев. - person Svante; 27.04.2009
comment
Это решение просто великолепно. - person user541686; 19.01.2011
comment
На самом деле это будет O(n): вы касаетесь узла только постоянное количество раз (обратите внимание, что внутренний цикл while касается только путей, которые идут вправо, и только когда curr был родителем крайнего левого узла пути при входе во внешний цикл while; это происходит один раз при входе в поддерево и при выходе из него.Тогда size of those paths combined = O(size of the whole tree). - person jpalecek; 25.04.2012
comment
Что делает assert? Как изменить его на С++? - person Somnium; 31.01.2015
comment
Вот подробное обсуждение этого алгоритма: forrestyu.net/art/ - person Rufflewind; 14.02.2016
comment
Как это не портит дерево? как curr.right = null не портит? @Сванте - person shinzou; 06.10.2017
comment
@shinzou: curr.right ранее был установлен как обратная ссылка (семью строками ранее: curr.right = parent), установка для него значения null просто восстанавливает дерево, каким оно было раньше. Идея всего этого заключается в том, что временная перезапись существующих нулевых ссылок в данных для хранения продолжений не требует дополнительного места. - person Svante; 07.10.2017
comment
@Rufflewind, эта ссылка не работает - person MattCochrane; 21.05.2020

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

В этом примере размер стека остается постоянным, поэтому дополнительная память не используется. Конечно, как указал Мердад, ссылки на родителей можно рассматривать как пространство O(n), но это скорее свойство дерева, чем свойство алгоритма.

Если вас не волнует порядок, в котором вы пересекаете дерево, вы можете назначить интегральное отображение узлам, где корень равен 1, дочерние элементы корня — 2 и 3, дочерние элементы — 4, 5, 6, 7 и т. д. Затем вы перебираете каждую строку дерева, увеличивая счетчик и получая доступ к этому узлу по его числовому значению. Вы можете отслеживать максимально возможный дочерний элемент и останавливать цикл, когда ваш счетчик проходит его. С точки зрения времени это чрезвычайно неэффективный алгоритм, но я думаю, что он занимает O (1) места.

(Я позаимствовал идею нумерации из куч. Если у вас есть узел N, вы можете найти дочерние узлы в 2N и 2N+1. Вы можете работать в обратном порядке от этого числа, чтобы найти родителя дочернего узла.)

Вот пример этого алгоритма в действии на C. Обратите внимание, что нет никаких malloc, кроме создания дерева, и что нет рекурсивных функций, что означает, что стек занимает постоянное пространство:

#include <stdio.h>
#include <stdlib.h>

typedef struct tree
{
  int value;
  struct tree *left, *right;
} tree;

tree *maketree(int value, tree *left, tree *right)
{
  tree *ret = malloc(sizeof(tree));
  ret->value = value;
  ret->left = left;
  ret->right = right;
  return ret;
}

int nextstep(int current, int desired)
{
  while (desired > 2*current+1)
      desired /= 2;

  return desired % 2;
}

tree *seek(tree *root, int desired)
{
  int currentid; currentid = 1;

  while (currentid != desired)
    {
      if (nextstep(currentid, desired))
    if (root->right)
      {
        currentid = 2*currentid+1;
        root = root->right;
      }
    else
      return NULL;
      else
    if (root->left)
      {
        currentid = 2*currentid;
        root = root->left;
      }
    else
      return NULL;
    }
  return root;  
}


void traverse(tree *root)
{
  int counter;    counter = 1; /* main loop counter */

  /* next = maximum id of next child; if we pass this, we're done */
  int next; next = 1; 

  tree *current;

  while (next >= counter)
    {   
      current = seek(root, counter);
      if (current)
      {
          if (current->left || current->right)
              next = 2*counter+1;

          /* printing to show we've been here */
          printf("%i\n", current->value);
      }
      counter++;
    }
}

int main()
{
  tree *root1 =
    maketree(1, maketree(2, maketree(3, NULL, NULL),
                            maketree(4, NULL, NULL)),
                maketree(5, maketree(6, NULL, NULL),
                            maketree(7, NULL, NULL)));

  tree *root2 =
      maketree(1, maketree(2, maketree(3,
          maketree(4, NULL, NULL), NULL), NULL), NULL);

  tree *root3 =
      maketree(1, NULL, maketree(2, NULL, maketree(3, NULL,
          maketree(4, NULL, NULL))));

  printf("doing root1:\n");
  traverse(root1);

  printf("\ndoing root2:\n");
  traverse(root2);

  printf("\ndoing root3:\n");
  traverse(root3);
}

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

person Kyle Cronin    schedule 26.04.2009
comment
Брайан, каждый бит числа покажет тебе, в каком направлении ты пойдешь. - person mmx; 26.04.2009
comment
Но как бы вы получили доступ, например, к узлу № 7, не используя дополнительное пространство? Рекурсивный поиск такого узла занял бы место, и если бы вы сохранили все узлы в другой структуре данных, это также заняло бы дополнительное пространство. - person Brian R. Bondy; 26.04.2009
comment
@Brian Вы можете вычесть 1 и разделить на 2, чтобы увидеть, что 3 является родителем 7. Точно так же вы можете вычесть 1 и снова разделить на 2, чтобы увидеть, что 1 является родителем 3. Поэтому все, что вам нужно, — это цикл, который будет вычислять следующий шаг на пути к нужному узлу от текущего узла. - person Kyle Cronin; 26.04.2009
comment
@Mehrdad: Правильно, например, у вас может быть тяжелое левое дерево, и это вообще не работает. - person Brian R. Bondy; 26.04.2009
comment
@Mehrdad На самом деле все, что вам нужно сделать, это проверить, существует ли дочерний элемент, прежде чем пытаться получить к нему доступ, поэтому он должен работать для любого двоичного дерева. - person Kyle Cronin; 26.04.2009
comment
@nobody_: ваша идея хороша, но вы должны отметить, что она работает только в полных бинарных деревьях, и с чисто теоретической точки зрения вам все еще нужно битовое пространство O (высота) для хранения того, где вы были. - person mmx; 26.04.2009
comment
@nobody_: Нет, это не сработает, поскольку вы возвращаетесь из корня, вы понятия не имеете о количестве дочерних элементов ваших внуков. - person mmx; 26.04.2009
comment
Как добраться до каждого элемента на уровне 4? Или, если уж на то пошло, уровень N? - person Brian R. Bondy; 26.04.2009
comment
@Mehrdad: каждый раз, когда вы получаете доступ к узлу, вы проходите от корня. В этом обходе вы проверяете, существует ли дочерний элемент, прежде чем пытаться получить к нему доступ. - person Kyle Cronin; 26.04.2009
comment
никто_: Я согласен с этим. Предположим, что бинарное дерево сильно несбалансировано (просто связанный список с левыми узлами и, следовательно, с N уровнями). Вам нужно O (N) бит для хранения числа. - person mmx; 26.04.2009
comment
@Brian: чтобы получить каждый элемент на уровне N, вы получаете доступ к узлам с номерами от 2 ^ (N-1) до 2 ^ N-1 в цикле. - person Kyle Cronin; 26.04.2009
comment
@Mehrdad: я не совсем понимаю, что вы имеете в виду под битовым подходом, но даже если это имеет смысл .... O (N) = O (C * N). Даже если N равно 1 би, у вас все еще есть пространство O (N). - person Brian R. Bondy; 26.04.2009
comment
@nobody_: Можете ли вы привести пример того, как вы получаете доступ к узлу по его номеру? - person Brian R. Bondy; 26.04.2009
comment
@Brian: Битовый подход, который я имел в виду, - это именно то, что вы сказали. 2^N требует N бит для хранения и занимает O(N) места. - person mmx; 26.04.2009

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

Кроме того, вы можете построить другое бинарное дерево, когда уничтожите первое. Вам потребуется некоторое микроуправление памятью, чтобы убедиться, что пиковая память никогда не превышает размер исходного дерева плюс, возможно, небольшая константа. Однако это, вероятно, потребует значительных вычислительных затрат.

EDIT: Способ есть! Вы можете использовать сами узлы, чтобы осветить обратный путь вверх по дереву, временно поменяв их местами. Когда вы посещаете узел, вы указываете его указатель left-child на его родителя, его указатель right-child на последний раз, когда вы повернули направо на своем пути (который в этот момент находится в указателе right-child родителя), и сохраняете его реальный дочерние элементы либо в указателе right-child теперь уже избыточного родителя, либо в вашем состоянии обхода, соответственно. следующий посещенный детский указатель left-child. Вам нужно сохранить некоторые указатели на текущий узел и его окрестности, но ничего «нелокального». Когда вы снова поднимаетесь по дереву, вы обращаете процесс вспять.

Я надеюсь, что смог как-то прояснить это; это просто грубый набросок. Вам нужно будет где-нибудь поискать (я уверен, что это упоминается где-то в «Искусстве компьютерного программирования»).

person Svante    schedule 26.04.2009
comment
+1, я больше прокомментировал этот метод со ссылкой на ваш ответ в моем ответе. - person Brian R. Bondy; 26.04.2009
comment
Гениально, но ужасно. Просто имейте указатель вверх в каждом элементе. Это намного проще, менее болезненно, хорошо понятно и хорошо известно. - person ; 22.05.2009

Сохранение дерева и использование пространства O(1) возможно, если...

  • Каждый узел имеет фиксированный размер.
  • Все дерево находится в непрерывной части памяти (т.е. в массиве)
  • Вы не перебираете дерево, вы просто перебираете массив

Или если вы уничтожите дерево во время его обработки...:

  • @Svante предложил эту идею, но я хотел немного расширить как, используя деструктивный подход.
  • Как? Вы можете продолжать брать самый левый узел leaf в дереве (for(;;) node = node->left etc... , затем обработать его, а затем удалить. Если самый левый узел в дерево не является конечным узлом, тогда вы обрабатываете и удаляете самый левый конечный узел правого узла.Если правый узел не имеет дочерних элементов, вы обрабатываете и удаляете его.

Как это не сработает...

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

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

person Brian R. Bondy    schedule 26.04.2009
comment
Вы ошибаетесь, упражнение 21 Knuth TAOCP I.2.3.1 относится как минимум к трем алгоритмам обхода дерева в пространстве O (1) без разрушения исходного дерева (хотя они временно модифицируют его на месте). - person Anton Tykhyy; 27.04.2009
comment
@nobody_: я не учитывал случаи, когда вам разрешено изменять дерево. Но я привел 2 примера, так что наверняка есть :) В любом случае, я изменил свой ответ, убрав недопустимые части. - person Brian R. Bondy; 27.04.2009
comment
Для тех, у кого нет TAOCP, @AntonTykhyy имеет в виду алгоритмы обхода дерева Морриса, которые выполняются за время O (n) и вспомогательное пространство O (1). Эта страница может помочь: stackoverflow.com/questions/5502916/. Кроме того, кто-то разместил ниже алгоритм предварительного заказа Морриса. - person John Kurlak; 07.02.2013

Указатели от узлов к их предкам можно получить без (ну, два бита на узел) дополнительного хранилища, используя структуру, называемую деревом с нитями. В многопоточном дереве нулевые ссылки представлены битом состояния, а не нулевым указателем. Затем вы можете заменить нулевые ссылки указателями на другие узлы: левые ссылки указывают на последующий узел в неупорядоченном обходе, а правые ссылки — на предшествующий. Вот диаграмма с большим количеством Unicode (X представляет узел заголовка, используемый для управления деревом):

                                         ╭─┬────────────────────────────────────────╮
   ╭─────────────────────────▶┏━━━┯━━━┯━━▼┓│                                        │
   │                        ╭─╂─  │ X │  ─╂╯                                        │ 
   │                        ▼ ┗━━━┷━━━┷━━━┛                                         │
   │                    ┏━━━┯━━━┯━━━┓                                               │
   │               ╭────╂─  │ A │  ─╂──╮                                            │
   │               ▼    ┗━━━┷━━━┷━━━┛  │                                            │    
   │        ┏━━━┯━━━┯━━━┓    ▲         │        ┏━━━┯━━━┯━━━┓                       │
   │      ╭─╂─  │ B │  ─╂────┤         ├────────╂─  │ C │  ─╂───────╮               │
   │      ▼ ┗━━━┷━━━┷━━━┛    │         ▼        ┗━━━┷━━━┷━━━┛       ▼               │  
   │┏━━━┯━━━┯━━━┓ ▲          │   ┏━━━┯━━━┯━━━┓       ▲         ┏━━━┯━━━┯━━━┓        │
   ╰╂─  │ D │  ─╂─╯          ╰───╂   │ E │  ─╂╮      │        ╭╂─  │ F │  ─╂╮       │ 
    ┗━━━┷━━━┷━━━┛                ┗━━━┷━━━┷━━━┛▼      │        ▼┗━━━┷━━━┷━━━┛▼       │
                                    ▲ ┏━━━┯━━━┯━━━┓  │ ┏━━━┯━━━┯━━━┓ ▲ ┏━━━┯━━━┯━━━┓│
                                    ╰─╂─  │ G │   ╂──┴─╂─  │ H │  ─╂─┴─╂─  │ J │  ─╂╯
                                      ┗━━━┷━━━┷━━━┛    ┗━━━┷━━━┷━━━┛   ┗━━━┷━━━┷━━━┛

Когда у вас есть структура, сделать неупорядоченный обход очень и очень просто:

Inorder-Successor(p)
    p points to a node.  This routine finds the successor of p in
    an inorder traversal and returns a pointer to that node

    qp.right
    If p.rtag = 0 Then
        While q.ltag = 0 Do
            qq.left
        End While
    End If

    Return q
    

Дополнительную информацию о деревьях потоков можно найти в Art of Computer Programming Ch.2 §3.1 или разбросать по всему Интернету.

person Nietzche-jou    schedule 26.04.2009

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

РЕДАКТИРОВАТЬ в ответ на редактирование вопроса: если вы хотите перебрать все дерево, то это невозможно. Чтобы снова забраться на дерево, нужно знать, куда идти. Однако, если вы просто хотите выполнить итерацию по одному пути вниз по дереву, это может быть достигнуто за O(1) дополнительного пространства. Просто выполните итерацию по дереву, используя цикл while, сохраняя единственный указатель на текущий узел. Продолжайте движение вниз по дереву, пока не найдете нужный узел или не наткнетесь на конечный узел.

РЕДАКТИРОВАТЬ: Вот код для первого алгоритма (проверьте функцию iterate_constant_space() и сравните с результатами стандартной функции iterate()):

#include <cstdio>
#include <string>
using namespace std;

/* Implementation of a binary search tree. Nodes are ordered by key, but also
 * store some data.
 */
struct BinarySearchTree {
  int key;      // they key by which nodes are ordered
  string data;  // the data stored in nodes
  BinarySearchTree *parent, *left, *right;   // parent, left and right subtrees

  /* Initialise the root
   */
  BinarySearchTree(int k, string d, BinarySearchTree *p = NULL)
    : key(k), data(d), parent(p), left(NULL), right(NULL) {};
  /* Insert some data
   */
  void insert(int k, string d);
  /* Searches for a node with the given key. Returns the corresponding data
   * if found, otherwise returns None."""
   */
  string search(int k);
  void iterate();
  void iterate_constant_space();
  void visit();
};

void BinarySearchTree::insert(int k, string d) {
  if (k <= key) { // Insert into left subtree
    if (left == NULL)
      // Left subtree doesn't exist yet, create it
      left = new BinarySearchTree(k, d, this);
    else
      // Left subtree exists, insert into it
      left->insert(k, d);
  } else { // Insert into right subtree, similar to above
    if (right == NULL)
      right = new BinarySearchTree(k, d, this);
    else
      right->insert(k, d);
  }
}

string BinarySearchTree::search(int k) {
  if (k == key) // Key is in this node
    return data;
  else if (k < key && left)   // Key would be in left subtree, which exists
    return left->search(k); // Recursive search
  else if (k > key && right)
    return right->search(k);
  return "NULL";
}

void BinarySearchTree::visit() {
  printf("Visiting node %d storing data %s\n", key, data.c_str());
}

void BinarySearchTree::iterate() {
  visit();
  if (left) left->iterate();
  if (right) right->iterate();
}

void BinarySearchTree::iterate_constant_space() {
  BinarySearchTree *current = this, *from = NULL;
  current->visit();
  while (current != this || from == NULL) {
    while (current->left) {
      current = current->left;
      current->visit();
    }
    if (current->right) {
      current = current->right;
      current->visit();
      continue;
    }
    from = current;
    current = current->parent;
    if (from == current->left) {
      current = current->right;
      current->visit();
    } else {
      while (from != current->left && current != this) {
        from = current;
        current = current->parent;
      }
      if (current == this && from == current->left && current->right) {
        current = current->right;
        current->visit();
      }
    }
  }
}

int main() {
  BinarySearchTree tree(5, "five");
  tree.insert(7, "seven");
  tree.insert(9, "nine");
  tree.insert(1, "one");
  tree.insert(2, "two");
  printf("%s\n", tree.search(3).c_str());
  printf("%s\n", tree.search(1).c_str());
  printf("%s\n", tree.search(9).c_str());
  // Duplicate keys produce unexpected results
  tree.insert(7, "second seven");
  printf("%s\n", tree.search(7).c_str());
  printf("Normal iteration:\n");
  tree.iterate();
  printf("Constant space iteration:\n");
  tree.iterate_constant_space();
}
person moinudin    schedule 26.04.2009
comment
Когда вы идете вверх по дереву... как вы добираетесь до конца каждого узла в дереве? - person Brian R. Bondy; 26.04.2009
comment
текущий = корень; в то время как текущий->левый существует: текущий = текущий->левый - person moinudin; 26.04.2009
comment
@marcog: Да, вы, безусловно, можете добраться до одного узла .... Но я сказал, как добраться до конца каждого узла в дереве? - person Brian R. Bondy; 26.04.2009
comment
Используя условие, которое я упомянул, чтобы перейти к правому потомку, когда вы только что вышли из левого потомка. - person moinudin; 26.04.2009
comment
И как только вы обработаете этот правый дочерний элемент, как вы получите второго левого дочернего элемента? а потом левый самый правый ребенок? ... Вам нужно сохранить, какие пути вы выбрали. - person Brian R. Bondy; 26.04.2009
comment
Предположим, вы находитесь на самом левом листе. Затем вы подходите к его родителю. Поскольку вы подошли к родителю от его левого дочернего элемента, вы спуститесь к его правому дочернему элементу. Предположим для простоты, что этот правый потомок также является листом. Вы возвращаетесь к его родителю. Поскольку вы подошли к родителю от его правого потомка, вы подошли к родителю родителя. Используя этот алгоритм, вы можете обойти все дерево без сохранения путей. - person moinudin; 26.04.2009
comment
@Marco: этот подход не работает, нет места, чтобы вспомнить, шли ли вы влево или вправо в последний раз, когда посещали узел. Попробуйте закодировать это (я сделал). - person Anton Tykhyy; 27.04.2009
comment
@Anton Переходя от дочернего элемента к его родителю, вы передаете адрес дочернего элемента родителю. Затем родитель сравнивает его указатель с левым дочерним элементом. Если они равны, то вы произошли от левого дочернего элемента, в противном случае вы произошли от правого дочернего элемента. - person moinudin; 27.04.2009
comment
@Anton отредактировал мой ответ, включив код, чтобы доказать, что это возможно :) - person moinudin; 27.04.2009

«Структуры данных и их алгоритмы» Гарри Льюиса и Ларри Дененберга описывают обход инверсии ссылок для обхода постоянного пространства двоичного дерева. Для этого вам не нужен родительский указатель на каждом узле. Обход использует существующие указатели в дереве для хранения пути для обратного отслеживания. Необходимы 2-3 дополнительные ссылки на узлы. Плюс немного на каждом узле, чтобы отслеживать направление обхода (вверх или вниз) при движении вниз. В моей реализации этого алгоритма из книги профилирование показывает, что на этот обход уходит гораздо меньше памяти/процессорного времени. Реализация на Java находится здесь.

person quiet_penguin    schedule 03.11.2013
comment
Вам нужна дополнительная структура данных для хранения этого битового массива, это не пространство O (1). - person shinzou; 10.10.2017

http://en.wikipedia.org/wiki/XOR_linked_list

закодируйте свой родительский узел в указатели листа

person ticktock    schedule 24.04.2012

Да, это возможно. Вот мой пример итерации, которая имеет временную сложность O (n) и пространственную сложность O (1).

using System;
                    
public class Program
{
    public class TreeNode {
      public int val;
      public TreeNode left;
      public TreeNode right;
      public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
          this.val = val;
          this.left = left;
          this.right = right;
      }
    }
    public static void Main()
    {
        TreeNode left = new TreeNode(1);
        TreeNode right = new TreeNode(3);
        TreeNode root = new TreeNode(2, left, right);
        
        TreeNode previous = null;
        TreeNode current = root;
        TreeNode newCurrent = null;
        
        while(current != null) {
            if(current.left == null) {
                if(current.right == null) {
                    if(previous == null) {
                        Console.WriteLine(current.val);
                        break;
                    }
                    Console.WriteLine(current.val);
                    current = previous;
                    previous = previous.left;
                    current.left = null;
                } else {
                    newCurrent = current.right;
                    current.right = null;
                    current.left = previous;
                    previous = current;
                    current = newCurrent;
                }
            } else {
                newCurrent = current.left;
                current.left = previous;
                previous = current;
                current = newCurrent;
            }
        }
    }
}

Каждый раз, когда вы видите Console.WriteLine(current.val);, вы должны поместить свой код для обработки значений.

person some1 here    schedule 11.03.2021

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

person Community    schedule 26.04.2009

Можно ли перебирать бинарное дерево во вспомогательном пространстве O(1).

struct node { node * father, * right, * left; int value; };

Эта структура позволит вам двигаться на 1 шаг в любом направлении по бинарному дереву.
Но все же в итерации вам нужно сохранить путь!

person Khaled Khunaifer    schedule 24.04.2012