Как пройти бинарное дерево за O (n) время без дополнительной памяти - PullRequest
6 голосов
/ 05 апреля 2009

При наличии бинарного дерева с целым числом, указателями влево и вправо, как можно пройти по дереву за O (n) времени и O (1) дополнительной памяти (без стека / очереди / рекурсии)?

Этот парень дал решение, которое не является O (n) общим временем, которое закодировало текущий путь как целое число (и таким образом работает для деревьев ограниченной глубины).

Ищу классическое решение

(SPOILER)

, который закодировал родителя каждого узла в дочерних элементах.

Ответы [ 5 ]

6 голосов
/ 05 апреля 2009

Любая хорошая книга по алгоритмам будет иметь этот алгоритм, смотрите, например. в Кнуте (TAOCP I.2.3.1 Обход бинарных деревьев, упражнение 21). Однако, поскольку этот алгоритм изменяет дерево на месте, вы должны соблюдать осторожность extreme в многопоточной среде.

Вы также можете использовать резьбовые деревья (см. Кнут).

3 голосов
/ 07 апреля 2009

Основная идея аналогична алгоритму инверсии списка, с одним сверхъестественным хитрым взломом (с теоретической точки зрения, вероятно, обманом), основанным на том факте, что указатели (на всех языках, которые в настоящее время известны людям) , 0 режим 4 как целые числа.

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

Псевдокод следует:

current = root->left
next = current
while (current != null) {
  next = current->left
  current->left = static_cast<int>(prev) + 1 // ugly hack.
  current = next
}
status = done
while (current != root or status != done) {
  if (status = done) {
     if (static_cast<u32>(current->left) %4 = 1) {
         next = static_cast<u32>(current->left) -1
         current->left = prev
         status = middle
     }
     else {
         next = current->right
         current->right = prev
         status = done
     }
     prev = current
     current = next
  }
  else if (status == left) {
     if (current->left) {
       prev = current->left
       current->left = static_cast<u32>(next) +1
       next = current
     }
     else
       status = middle
  }
  else if (status == right) {
     if (current->right) {
        prev = current->right;
        current ->right = next;
        next = current
     }
     else
       status = done
  }
  else {// status == middle
     work_on_node(current)
     status = right
  }
}
1 голос
/ 06 апреля 2009

Алгоритм этого парня интересен, но нужно отметить, что ему требуется O (log n) дополнительных бит пространства для прохождения двоичного дерева с n узлами. Требования к пространству должны измеряться в битах, а не в байтах - обычно они сводятся к одному и тому же при использовании обозначения Big Oh, но в случаях, подобных этому, указывается, почему важно проводить различие.

Чтобы увидеть это, спросите, как можно перебрать дерево с более чем 2 ^ 32-1 узлами, используя одно целое число памяти (на 32-битной платформе).

0 голосов
/ 12 сентября 2010

Вот еще одно решение

http://sites.google.com/site/debforit/efficient-binary-tree-traversal-with-two-pointers

Но мне было интересно, есть ли способ сделать это в таких языках, как Java, которые НЕ имеют указателей в истинном смысле.

0 голосов
/ 06 апреля 2009

Используйте память O (1) для запоминания указателя "последний посещенный узел". Вы можете инициализировать его равным 0 или некоторому неизвестному значению.

Чтобы пройтись по дереву, начните с корневого узла. Посмотрите на обоих детей узла. Если ваш «последний посещенный узел» равен правому узлу, перейдите к родительскому узлу. Если «последний посещенный» равен левому узлу, перейдите к правому узлу. Еще шаг к левому узлу.

Повторяйте, пока не закончите ходить по всему дереву. Единственный реальный ум - использовать одну переменную, чтобы запомнить, откуда вы пришли, чтобы решить, куда идти дальше. Это делает обход детерминированным.

В итоге вы сделаете O (n) шагов. Вы будете посещать каждый средний узел три раза и каждый лист один раз, так что вы все еще O (N). Хранение O (1).

...