Вращение двоичного дерева - PullRequest
3 голосов
/ 02 августа 2011

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

Алгоритм работает как на бумаге, но при выполнении на машине он хорошо ... пропускает узлы дерева.

Этот метод используется для поворота узла влево: http://pastebin.com/mPHj29Af

bool avl_search_tree::avl_tree_node::rotate_left()
{
    if (_right_child != NULL) {
        avl_tree_node *new_root = _right_child;
 
        if (_parent != NULL) {
            if (_parent->_left_child == this) {
                _parent->_left_child = new_root;
            } else {
                _parent->_right_child = new_root;
            }
        }
 
        new_root->_parent = _parent;
        _parent = new_root;
 
        _right_child = new_root->_left_child;
        new_root->_left_child = this;
 
        if (_right_child != NULL) {
            _right_child->_parent = this;
        }
 
        //update heights
        update_height();
        new_root->update_height();
 
        return true;
    }
 
    return false;
}

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

Любая помощь в выявлении проблемы очень важна, так как я начинаю идтиmad.

Для справки: если я не использую никаких вращений, дерево не будет пропускать узлы, и оно работает как обычное несбалансированное двоичное дерево поиска (для вставки и поиска).

Редактировать: Из-за комментария AJG85 я добавлю наблюдения:

Я добавил printf 'check' в метод деструктора avl_search_tree :: avl_tree_node, который будет печатать значение ключа (в моем случае 32-битные целые числа) перед очисткойи к методу вставки дерева avl_search_tree, который будет печатать только что вставленный ключ.

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

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

bool avl_search_tree::insert(const int&) : 1
bool avl_search_tree::insert(const int&) : 2
bool avl_search_tree::insert(const int&) : 3
bool avl_search_tree::insert(const int&) : 4
bool avl_search_tree::insert(const int&) : 5
bool avl_search_tree::insert(const int&) : 6
bool avl_search_tree::insert(const int&) : 7
bool avl_search_tree::insert(const int&) : 8
avl_search_tree::avl_tree_node::~avl_tree_node() : 1

Это означает, что все вставки были успешными, но был удален только корень.

С помощьюAVL Balancing прокомментировал, что он работает как обычное двоичное дерево поиска.Вывод терминала:

bool avl_search_tree::insert(const int&) : 1
bool avl_search_tree::insert(const int&) : 2
bool avl_search_tree::insert(const int&) : 3
bool avl_search_tree::insert(const int&) : 4
bool avl_search_tree::insert(const int&) : 5
bool avl_search_tree::insert(const int&) : 6
bool avl_search_tree::insert(const int&) : 7
bool avl_search_tree::insert(const int&) : 8
avl_search_tree::avl_tree_node::~avl_tree_node() : 1
avl_search_tree::avl_tree_node::~avl_tree_node() : 2
avl_search_tree::avl_tree_node::~avl_tree_node() : 3
avl_search_tree::avl_tree_node::~avl_tree_node() : 4
avl_search_tree::avl_tree_node::~avl_tree_node() : 5
avl_search_tree::avl_tree_node::~avl_tree_node() : 6
avl_search_tree::avl_tree_node::~avl_tree_node() : 7
avl_search_tree::avl_tree_node::~avl_tree_node() : 8

Что означает, что все правильно очищено.

Теперь ... как я пришел к выводу, что методы ротации - это проблемы?Под прокомментированной подпрограммой балансировки AVL я добавил строку, которая поворачивает каждый вновь вставленный узел влево.Результат?Так же, как если бы подпрограмма балансировки AVL была включена.

А что касается метода update_height (), он никак не изменяет структуру дерева.

Надеюсь, это прояснит это.

Edit 2:

Чтобы прояснить еще кое-что, его способ реализации деструктора avl_tree_node:

avl_search_tree::avl_tree_node::~avl_tree_node()
{
    printf("%s : %d\n", __PRETTY_FUNCTION__, *_key);

    if (_left_child != NULL) {
        delete _left_child;
    }

    if (_right_child != NULL) {
        delete _right_child;
    }

    if (_key != NULL) {
        delete _key;
    }
}

_left_child и _right_child являются указателями на объекты avl_tree_node, расположенные накуча.

Редактировать 3:

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

В основном корень дерева всегда указывал на первый вставленный узел и без обновления указателя, когданеобходимо, чтобы мои методы rotate вытекли из корня нового дерева, которое было настроено правильно.:)

Спасибо AGJ85!

Ответы [ 3 ]

3 голосов
/ 03 августа 2011

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

В основном корень дерева всегда указывал на первый вставленный узел и без обновления указателя, когданеобходимо, чтобы мои методы rotate вытекли из корня нового дерева, которое было настроено правильно.:)

2 голосов
/ 02 августа 2011

РЕДАКТИРОВАТЬ - Черт - Я не видел, что проблема уже решена (ответ на вопрос). Тем не менее, возможно, есть несколько советов, которые не помогут ответить на этот вопрос.

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

_right_child = new_root->_left_child;

и проблема в том, что вы, возможно, уже переписали new_root->_left_child в строке ...

_parent->_left_child = new_root;

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

avl_tree_node *orig_parent      = _parent;
avl_tree_node *orig_this        = this;
avl_tree_node *orig_left_child  = _left_child;
avl_tree_node *orig_right_child = _right_child;

Затем используйте локальные переменные orig_ в качестве источников для последующих назначений. Это избавляет от некоторого беспокойства по поводу потока данных через различные указатели во время ротации. Оптимизатор должен избавиться от любой лишней работы, о которой стоит беспокоиться, и в любом случае ее не так много.

Пара дополнительных очков ...

Во-первых, стандарты C ++ (и C) резервируют идентификаторы с ведущими подчеркиваниями и с двойными подчеркиваниями. Утверждается, что вы можете получить неожиданное взаимодействие со стандартными библиотеками и библиотеками, предоставляемыми компилятором, если вы не уважаете это - я думаю, что это должно быть связано с макросами для идентификаторов элементов. Конечные подчеркивания в порядке - я склонен использовать их для включения охраны.

Общепринятым соглашением для переменных-членов является добавление ведущего m или m_. Еще более распространенным, вероятно, является отсутствие какого-либо специального префикса или суффикса вообще.

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

Наконец, общий совет - попытка «пробного запуска» подобного алгоритма может легко сбить вас с толку, если вы строго не пройдете его пошагово и не проверите все источники информации, которые имеют отношение к делу. (я уже изменил это?) на каждом шагу. Очень легко привыкнуть пропускать некоторые детали для скорости. Полезный пробный прогон с помощью машины - это пошаговое выполнение кода в отладчике и проверка соответствия результатов на каждом шаге пробному прогону бумаги.

РЕДАКТИРОВАТЬ - еще одно примечание - я не буду называть это подсказкой, потому что я не уверен в этом контексте. Я обычно реализую узлы структуры данных с простыми структурами - без скрытия данных, немногие, если есть функции-члены. Большая часть кода хранится отдельно от структуры данных, часто в классе «инструмента». Я знаю, что это нарушает старый принцип ООП «форма рисует сама», но IMO работает лучше на практике.

1 голос
/ 04 августа 2011

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

На более высоком уровне обзора, техника, которую я использовал, чтобы избежать проблем с AVL Tree или Красно-черное дерево , заключается в использовании вместо AA Tree , которая имеет аналогичную производительность, используя пространство O (n) и время O (log n) для вставки, удаления и поиска. Однако деревья АА значительно проще в кодировании.

...