Это не домашнее задание, и мне не нужно на него отвечать, но теперь я стал одержим:)
Проблема в следующем:
Я нашел решение этой проблемы в Интернете около года назад, но теперь я забыл об этом и хочу знать:)
Уловка, насколько я помню, заключалась в использовании дерева для реализовать очередь, пользуясь деструктивным характером алгоритма. Когда вы связываете список, вы также помещаете элемент в очередь.
Каждый раз, когда я пытаюсь решить эту проблему, я теряю узлы (например, каждый раз, когда я связываю следующий узел / добавляю в очередь), мне требуется дополнительное хранилище, или я не могу понять запутанный метод, который мне нужно получить обратно к узлу, у которого есть нужный мне указатель.
Мне была бы полезна даже ссылка на эту оригинальную статью / пост :) Google не доставляет мне радости.
Изменить:
Джереми указал, что существует довольно простой (и хорошо известный ответ), если у вас есть родительский указатель.Хотя теперь я думаю, что он прав относительно исходного решения, содержащего родительский указатель, я действительно хотел решить проблему без него :)
В уточненных требованиях используется это определение для узла:
struct tree_node
{
int value;
tree_node* left;
tree_node* right;
};
Вы можете построить связный список по левым дочерним элементам дерева. В то же время правые дети этого списка используются для хранения очереди следующих поддеревьев для вставки в хвост.
(правка: переписано для ясности)
root
, tail
начинается с левого дочернего узла root
:
tail
с правым ребенком root
, bubble-to
, начинающимся с root
и bubble-from
всегда левый ребенок bubble-to
,
bubble-to
с правым ребенком ´bubble-from`, bubble-to
и bubble-from
к их левым детям, bubble-from
не достигнет tail
, tail
к его левому дочернему элементу, tail
не равен null. head
. Теперь единый связный список идет вдоль левых дочерних элементов. Начальное дерево (я считаю, что это должно быть полное дерево; если узлы отсутствуют, они должны быть с конца. Можно попробовать придать смысл в других случаях, но для этого есть несколько разных возможностей, и это предполагает много возни):
1
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \ / \ / \
8 9 A B C D E F
Обратите внимание, что это не обязательно значения узлов, они просто пронумерованы в целях отображения.
Дерево после 1 итерации (обратите внимание на формирование списка от 1
до 3
и очередь поддеревьев с корнями в 4
и 5
):
head
v
1
/ \
tail 2 4
v / \ / \
3 5 8 9
/ \ / \
6 7 A B
/ \ / \
C D E F
после 3 итераций (список теперь от 1
до 5
, а очередь содержит поддеревья, укорененные в 6
до 9
):
head
v
1
/ \
2 6
/ \ / \
3 7 C D
/ \ / \
4 8 E F
/ \
tail > 5 9
/ \
A B
и после 8 итераций (почти готово):
head
v
1
/ \
2 B
/ \
3 C
/ \
4 D
/ \
5 E
/ \
6 F
/
7
/
8
/
9
/
tail > A
Это класс узла:
(defclass node ()
((left :accessor left)
(right :accessor right)
(value :accessor value)))
Полезный помощник:
(defmacro swap (a b)
`(psetf ,a ,b
,b ,a))
Функция преобразования (edit: переписано для ясности):
(defun flatten-complete-tree (root)
(loop for tail = (and root (left root)) then (left tail)
while tail
do (swap (left tail) (right root))
(loop for bubble-to = root then (left bubble-to)
for bubble-from = (left bubble-to)
until (eq bubble-from tail)
do (swap (right bubble-to) (right bubble-from))))
root)
Я переписал вышеизложенное, чтобы позволить произвольные зубчатые деревья. Однако восстановить исходное дерево из этого уже нельзя.
(defun flatten-tree (root)
;; Внутренний цикл здесь удерживает head
в корне пока еще нерасщепленного поддерева,
;; т.е. в узле первой ветви,
;; в то же время проглаживая все от корня неразветвленные уровни влево.
;; Это заканчивается nil
, когда дерево полностью сплющено.
(loop for head = (loop for x = (or head root) then (left x)
do (when (and x (null (left x)))
(swap (left x) (right x)))
until (or (null x) (right x))
finally (return x))
for tail = (and head (left head)) then (left tail)
while head
do (swap (left tail) (right head))
;; Этот внутренний цикл является O(n) вставкой в очередь
(loop for bubble-to = head then (left bubble-to)
for bubble-from = (left bubble-to)
until (eq bubble-from tail)
do (swap (right bubble-to) (right bubble-from))))
;; Наконец, возвращаем исходный корень.
root)
Прежде всего, я предполагаю, что у ваших узлов есть «родительское» поле, которое указывает на их родительский элемент.Либо так, либо вам нужен стек, чтобы иметь возможность перемещаться вверх по дереву (и, таким образом, не удается достичь этого требования к вспомогательной памяти O (1)).
Существует хорошо известная итерация по порядку, которая является итеративной и находится в пространстве O (1). Предположим, например, что вы хотите распечатать элементы по порядку. По сути, когда вы проходите по бинарному дереву, вы должны решить в любой момент в любом заданном узле, хотите ли вы переместиться ВВЕРХ (к родительскому элементу), ВЛЕВО (к левому дочернему элементу) или ВПРАВО. Идея этого алгоритма состоит в том, чтобы основывать это решение на том, откуда вы пришли:
Хорошо: это алгоритм, который вы берете за основу. Теперь ясно, что если вы деструктивно модифицируете его в связанный список, вам следует изменять узел только тогда, когда вы больше не собираетесь его посещать. Это когда вы подходите справа. В этот момент вы должны решить, каким будет преемник этого узла ...?
Ну, вам нужно постоянно отслеживать два указателя: один на наименьший узел, который вы посетили, и один для самого большого узла, который вы посетили в текущем корневом поддереве.Вы используете наименьший в качестве преемника для узлов, которые вы в последний раз посещали, когда вы переходите от правильного дочернего элемента, и используете наибольший в качестве преемника для узлов, которые вы в последний раз посещали, пришедших откуда-то еще, потому что у них нет правильного дочернего элемента!
РЕДАКТИРОВАТЬ 1 : Я забыл сказать, что я неявно считаю, что «родительское» поле в двоичном дереве становится «следующим» полем в связанном списке - это то, что я изменяю на последнем шаге.
РЕДАКТИРОВАТЬ 3 : Оказалось, что следующая часть моего ответа не совсем отвечает на исходный вопрос (но то, что было сказано выше, все еще актуально).
РЕДАКТИРОВАТЬ 2 : Следуя понятному желанию Сванте, я выразил свое предложение об использовании левого поворота в некотором коде:
#include <stdlib.h>
#include <stdio.h>
typedef struct node *node;
struct node
{
int value;
node left;
node right;
};
node new_node(int value, node left, node right)
{
node n = (node) malloc(sizeof(struct node));
n->value = value; n->left = left; n->right = right;
return n;
}
int rotate_right(node tree)
{
if(tree != NULL && tree->left != NULL)
{
node
a = tree->left->left,
b = tree->left->right,
c = tree->right;
int tmp = tree->value;
tree->value = tree->left->value;
tree->left->value = tmp;
tree->left->left = b;
tree->left->right = c;
tree->right = tree->left;
tree->left = a;
return 1;
}
return 0;
}
Функция вращения не изящна, но, поскольку ее легко запутать, я попытался следовать точно такое же название, как в статье Википедии о вращениях . Узлы A, B, C названы так в моем коде; узлы P и Q не являются, и поскольку я решил не использовать указатели указателей, я вместо этого прибег к уловке переключения значений P и Q --- в вместо переключения мест. При наличии в нашем распоряжении «rotation_right» алгоритм преобразования довольно прост:
void convert_to_list(node tree)
{
if(tree != NULL) {
while(rotate_right(tree) != 0);
convert_to_list(tree->right);
}
}
Результирующее дерево представляет собой отсортированный связанный список, где указатель next списка - это то, что раньше было правый указатель в дереве. Наконец, вот тестовая программа:
int main()
{
node t =
new_node(4,
new_node(2, NULL, new_node(3, NULL, NULL)),
new_node(8, new_node(5, NULL, NULL), NULL));
convert_to_list(t);
for(; t != NULL; t = t->right)
printf("%d, ", t->value);
return 0;
}
Что ж, сейчас я не могу понять, как это помогает в этой ситуации, но это может дать вы ведущий. Существует метод, называемый «обращением указателя», используемый для итеративного обхода дерева без необходимости использования стека / очереди для хранения указателей - он в основном использовался для сборщиков мусора с малыми накладными расходами памяти. Идея, лежащая в основе этого, заключается в том, что, когда вы переходите к дочернему узлу, вы связываете этот указатель на дочерний элемент обратно с родительским, чтобы вы знали, куда вернуться, когда вы закончите с этим узлом. Таким образом, информация трассировки, которую вы обычно храните в стеке / очереди, теперь встроена в само дерево.
Я нашел следующее слайд-шоу с примером (к сожалению, в Google нет ничего лучше). В приведенном здесь примере показано, как пройти по двоичному дереву без дополнительной памяти.
Я не думаю, что нам нужны родительские указатели.Предположим индуктивно, что уровни от 0 до k-1 плюс контрольный узел были преобразованы в односвязный список на левых дочерних указателях, правые потомки которых указывают на узлы уровня k. Пройдитесь по списку, по очереди захватывая каждый «правый дочерний элемент» (узел уровня k) и вставляя его в конец списка, перезаписывая правый указатель, из которого он пришел, своим левым дочерним элементом, который скоро будет перезаписан. Когда мы достигли начального конца списка, мы расширили индуктивную гипотезу до k + 1.
РЕДАКТИРОВАТЬ: код
#include <cstdio>
struct TreeNode {
int value;
TreeNode *left;
TreeNode *right;
};
// for simplicity, complete binary trees with 2^k - 1 nodes only
void Flatten(TreeNode *root) {
TreeNode sentinel;
sentinel.right = root;
TreeNode *tail = &sentinel;
while (true) {
TreeNode *p = &sentinel;
TreeNode *old_tail = tail;
while (true) {
if ((tail->left = p->right) == NULL) {
return;
}
tail = p->right;
p->right = p->right->left;
if (p == old_tail) {
break;
}
p = p->left;
}
}
}
int main() {
const int n = 31;
static TreeNode node[1 + n];
for (int i = 1; i <= n; ++i) {
node[i].value = i;
if (i % 2 == 0) {
node[i / 2].left = &node[i];
} else {
node[i / 2].right = &node[i];
}
}
Flatten(&node[1]);
for (TreeNode *p = &node[1]; p != NULL; p = p->left) {
printf("%3d", p->value);
}
printf("\n");
}
Для записи, рекурсивная версия прекрасна (это на C #):
[Отредактировано: как указывает st0le, моя первая версия содержит новые! Простите меня, последние двадцать лет я кодил на декларативных языках. Вот исправленная версия.]
[Edit: двойные крысы. Это не в первую очередь.]
public static Tree<T> Flatten(Tree<T> t, Tree<T> u = null)
{
if (t == null) return u;
t.R = Flatten(t.L, Flatten(t.R, u));
t.L = null;
return t;
}
Это мой ответ, который работает. Теперь я понимаю, что это тот же подход, что и решение Сванте(!), хотя я строю дерево справа.
Для протокола, вот код на C#:
// Flatten a tree into place in BFS order using O(1) space and O(n) time.
// Here is an example of the transformation (the top-row indicates the
// flattened parts of the tree.
//
// a
// |---.
// b c
// |-. |-.
// d e f g
//
// becomes
//
// a-b-c
// | | |-.
// d e f g
//
// becomes
//
// a-b-c-d-e-f-g
//
public static void FlattenBFS(Tree<T> t)
{
var a = t; // We "append" newly flattened vertices after 'a'.
var done = (t == null);
while (!done)
{
done = true;
var z = a; // This is the last vertex in the flattened part of the tree.
var i = t;
while (true)
{
if (i.L != null)
{
var iL = i.L;
var iLL = iL.L;
var iLR = iL.R;
var aR = a.R;
i.L = iLL;
a.R = iL;
iL.L = iLR;
iL.R = aR;
a = iL;
done &= (iLL == null) & (iLR == null);
}
if (i == z)
{
break; // We've flattened this level of the tree.
}
i = i.R;
}
a = (a.R ?? a); // The a.R item should also be considered flattened.
}
}