Смекни!
smekni.com

Реализация АВЛ–деревьев через классы объектно–ориентированного программирования (стр. 2 из 6)

Таким образом, эффективность алгоритма в лучшем случае составит O(n log2n).

Удаление элемента из сбалансированного дерева.

Алгоритм удаления элемента из сбалансированного дерева будет выглядеть так:

1. Поиск по дереву.

2. Удаление элемента из дерева.

3. Восстановление сбалансированности дерева (обратный проход).

1 - ый и 2 - ый шаги необходимы, чтобы найти в дереве вершину, которая должна быть удалена.

3 - ий шаг представляет собой обратный проход от места, из которого взят элемент для замены удаляемого, или от места, из которого удален элемент, если в замене не было необходимости.

Операция удаления может потребовать перебалансировки всех вершин вдоль обратного пути к корню дерева, то есть порядка log(N) вершин.

Анализ операций над сбалансированным деревом.

Понятно, что в случае полного двоичного дерева мы получим сложность T(log(n)) (на каждом шаге размер дерева поиска будет сокращаться вдвое). Рассмотрим минимальное сбалансированное дерево (худший случай). Таким будет дерево, у которого для каждой вершины высота левого и правого поддеревьев различаются на 1. Для такого дерева можно записать следующую рекуррентную формулу для числа вершин (h – высота дерева):

Покажем, что h<log2(Nh). Для этого необходимо и достаточно показать, что 2h>Nh. Докажем последнее методом математической индукции.а) h=0: 20>N0=0; б) h=1: 21>N1=1; в) h>1: Пусть 2h-2>Nh-2 и 2h-1>Nh-1. Тогда 2h-2+2h-1>Nh-2+ Nh-1. И далее получаем 2h>1+2h-2+2h-1>1+Nh-2+ Nh-1=Nh, что и требовалось доказать.

Таким образом алгоритмы поиска/добавления/удаления элементов в сбалансированном дереве имеют сложность T(log(n)). Г.М. Адельсон -Вельский и Е.М. Ландис доказали теорему, согласно которой высота сбалансированного дерева никогда не превысит высоту идеально сбалансированного дерева более, чем на 45%.

Основные узлы АВЛ - деревьев.

АВЛ - деревья имеют структуру, похожую на бинарные деревья поиска. Все операции идентичны описанным для бинарных деревьев, за исключением методов Insert и Delete, которые должны постоянно отслеживать соотношение высот левого и правого поддеревьев узла. Для сохранения этой информации расширяем определение объекта TreeNode, включив поле balanceFactor (показатель сбалансированности), которое содержит разность высот правого и левого поддеревьев.

Left data balanceFactor right

AVLTreeNode

balanceFactor = height(right subtree) - height(left subtree)

Если balanceFactor отрицателен, то узел «перевешивает влево», так как высота левого поддерева больше, чем высота правого поддерева. При положительном balanceFactor узел «перевешивает вправо». Сбалансированный по высоте узел имеет balanceFactor = 0.

В АВЛ - дереве показатель сбалансированности должен быть в диапазоне [-1, 1]. На рисунке 3 изображены АВЛ - деревья с пометками -1, 0 и +1 на каждом узле, показывающими относительный размер левого и правого поддеревьев.

-1: Высота левого поддерева на 1 больше высоты правого поддерева.

0: Высоты обоих поддеревьев одинаковы.

+1: Высота правого поддерева на 1 больше высоты левого поддерева.


Рис. 1.

Рис. 2.

Используя свойства наследования, можно образовать класс AVLTreeNode на базе класса TreeNode. Объект типа AVLTreeNode наследует поля из класса TreeNode и добавляет к ним поле balanceFactor. Данные - члены left и right класса TreeNode являются защищенными, поэтому AVLTreeNode или другие производные классы имеют к ним доступ.

Рис. 3.

Спецификация класса AVLTreeNode.

Объявление:

// наследник класса TreeNode

template <class T>

class AVLTreeNode: public TreeNode<T>

{

private:

// дополнительный член класса balanceFactor используется методами класса AVLTree и позволяет избегать "перевешивания" узлов

int balanceFactor;

AVLTreeNode<T>* & Left(void);

AVLTreeNode<T>* & Right(void);

public:

// конструктор

AVLTreeNode(const T& item, AVLTreeNode<T> *lptr = NULL,

AVLTreeNode<T> *rptr = NULL, int balfac = 0);

// возвратить левый/правый указатель узла типа TreeNode преобразовав его к типу AVLTreeNode

AVLTreeNode<T> *Left(void) const;

AVLTreeNode<T> *Right(void) const;

// метод для доступа к новому полю данных

int GetBalanceFactor(void);

// методы класса AVLTree должны иметь доступ к Left и Right

friend class AVLTree<T>;

};

Описание:

Элемент данных balanceFactor является скрытым, так как обновлять его должны только выравнивающие баланс операции вставки и удаления. Доступ к полям - указателям осуществляется с помощью методов Left и Right. Новые определения для этих методов обязательны, поскольку они возвращают указатель на структуру AVLTreeNode.

Поскольку класс AVLTree образован на базе класса BinSTree, будем использовать деструктор базового класса и метод ClearList. Эти методы удаляют узлы с помощью оператора delete. В каждом случае указатель ссылается на объект типа AVLTreeNode, а не TreeNode. Так как деструктор базового класса TreeNode виртуальный, при вызове delete используется динамическое связывание и удаляется объект типа AVLTreeNode.

Пример:

Эта функция создает АВЛ - дерево, изображенное на рисунке 4. Каждому узлу присваивается показатель сбалансированности.

Рис. 4.

AVLTreeNode<char> *root; // корень АВЛ - дерева

void MakeAVLCharTree(AVLTreeNode<char>* &root)

{

AVLTreeNode<char> *a, *b, *c, *d, *e;

e = new AVLTreeNode<char>('E', NULL, NULL, 0);

d = new AVLTreeNode<char>('D', NULL, NULL, 0);

c = new AVLTreeNode<char>('C', e, NULL, -1);

b = new AVLTreeNode<char>('B', NULL, d, 1);

a = new AVLTreeNode<char>('A', b, c, 0);

root = a;

}

Реализация класса AVLTreeNode.

Конструктор класса AVLTreeNode вызывает конструктор базового класса и инициализирует balanceFactor.

// Конструктор инициализирует balanceFactor и базовый класс

// Начальные значения полей указателей по умолчанию, равные NULL

// инициализируют узел как лист

template <class T>

AVLTreeNode<T>::AVLTreeNode (const T& item,

AVLTreeNode<T> *lptr, AVLTreeNode<T> *rptr, int balfac):

TreeNode<T>(item, lptr, rptr), balanceFactor(balfac)

{}

Методы Left и Right в классе AVLTreeNode упрощают доступ к полям данных. При попытке обратиться к левому сыну с помощью метода Left базового класса возвращается указатель на объект типа TreeNode. Чтобы получить указатель на узел АВЛ - дерева, требуется преобразование типов.

Например:

AVLTreeNode<T> *p, *q;

q = p->Left(); // недопустимая операция

q = (AVLTreeNode<T> *)p->Left(); // необходимое приведение типа

Во избежание постоянного преобразования типа указателей мы определяем методы Left и Right для класса AVLTreeNode, возвращающие указатели на объекты типа AVLTreeNode.

template <class T>.

AVLTreeNode<T>* AVLTreeNode::Left(void)

{

return ((AVLTreeNode<T> *)left;

}

Класс AVLTree.

АВЛ - дерево представляет собой списковую структуру, похожую на бинарное дерево поиска, с одним дополнительным условием: дерево должно оставаться сбалансированным по высоте после каждой операции вставки или удаления. Поскольку АВЛ - дерево является расширенным бинарным деревом поиска, класс AVLTree строится на базе класса BinSTree и является его наследником.

Для выполнения АВЛ - условий следует изменить методы Insert и Delete. Кроме того, в производном классе определяются конструктор копирования и перегруженный оператор присвоения, так как мы строим дерево с большей узловой структурой.

Спецификация класса AVLTree.

Объявление:

// Значения показателя сбалансированности узла

const int leftheavy = - 1;

const int balanced = 0;

const int rightheavy = 1;

template <class T>

class AVLTree: public BinSTree<T>

{

private:

// выделение памяти

AVLTreeNode<T> *GetAVLTreeNode(const T& item,

AVLTreeNode<T> *lptr, AVLTreeNode<T> *rptr);

// используется конструктором копирования и оператором присваивания

AVLTreeNode<T> *CopyTree(AVLTreeNode<T> *t);

// используется методами Insert и Delete для восстановления АВЛ - условий после операций вставки/удаления

void SingleRotateLeft (AVLTreeNode<T>* &p);

void SingleRotateRight (AVLTreeNode<T>* &p);

void DoubleRotateLeft (AVLTreeNode<T>* &p);

void DoubleRotateRight (AVLTreeNode<T>* &p);

void UpdateLeftTree (AVLTreeNode<T>* &tree,

int &reviseBalanceFactor);

void UpdateRightTree (AVLTreeNode<T>* &tree,

int &reviseBalanceFactor);

// специальные версии методов Insert и Delete

void AVLInsert(AVLTreeNode<T>* &tree,

AVLTreeNode<T>* newNode, int &reviseBalanceFactor);

void AVLDelete(AVLTreeNode<T>* &tree,

AVLTreeNode<T>* newNode, int &reviseBalanceFactor);

public:

// конструкторы

AVLTree(void);

AVLTree(const AVLTree<T>& tree);

// оператор присваивания

AVLTree<T>& operator= (const AVLTree<T>& tree);

// стандартные методы обработки списков

virtual void Insert(const T& item);

virtual void Delete(const T& item);

};

Описание:

Константы leftheavy, balanced и rightheavy используются в операциях вставки/удаления для описания показателя сбалансированности узла. Метод GetAVLTreeNode управляет выделением памяти для экземпляра класса. По умолчанию balanceFactor нового узла равен нулю.

Рис. 5.


В этом классе заново определяется функция CopyTree для использования с конструктором копирования и перегруженным оператором присвоения. Несмотря на то, что алгоритм идентичен алгоритму для функции CopyTree класса BinSTree, новая версия корректно создает расширенные объекты типа AVLTreeNode при построении нового дерева.

Функции AVLInsert и AVLDelete реализуют методы Insert и Delete, соответственно. Они используют скрытые методы наподобие SingleRotateLeft. Открытые методы Insert и Delete объявлены виртуальными и подменяют соответствующие функции базового класса. Остальные операции наследуются от класса BinSTree.

Пример:

Код, приведенный ниже, создает деревья, приведенные на рисунке 5. После удаления из АВЛ - дерева (В) элемента 3 АВЛ - дерево принимает вид, изображенный на рисунке 5 (С). Цифра после двоеточия означает фактор сбалансированности.