Скачиваний:
123
Добавлен:
17.03.2015
Размер:
751.62 Кб
Скачать

Реализация класса BinSTree

Класс BinSTree описывает нелинейный список и базовые операции вставки, удаления и поиска элементов. Помимо методов обработки списков важную роль при реализации класса играет управление памятью. Скрытые методы CopyTree и DeleteTree используются конструктором, деструктором и оператором присваивания для размещения и уничтожения узлов списка.

Элементы данных класса BinSTree

Бинарное дерево поиска определяется своим указателем корня, который используется в операциях вставки (Insert), поиска (Find) и удаления (Delete). Класс BinSTree содержит элемент данных root, являющийся указателем корня и имеющий начальное значение NULL. Доступ к root осуществляется посредством метода GetRoot, разрешающего вызовы внешних функций прохождения. Второй указатель, current, определяет на дереве место для обновлений. Операция Find устанавливает current на совпавший узел, и этот указатель используется функцией Update для обновления данных. Методы Insert и Delete переустанавливают current на новый узел или на узел, заменивший удаленный. Объект BinSTree является списком, размер которого все время изменяется функциями Insert и Delete. Текущее число элементов в списке хранится в закрытом элементе данных size.

// Указатели на корень и на текущий узел

TreeNode<T> *root;

TreeNode<T> *current;

// Число элементов дерева

int size;

Управление памятью

Размещение и уничтожение узлов для методов Insert и Delete, а также для утилит CopyTree и DeleteTree выполняется посредством GetTreeNode и FreeTreeNode. Метод GetTreeNode аналогичен обсуждавшемуся выше. Он распределяет память и инициализирует поля данных и указателей в узле. FreeTreeNode непосредственно вызывает оператор удаления для освобождения памяти.

Конструктор, деструктор и оператор присваивания

Класс содержит конструктор, который инициализирует элементы данных. Конструктор копирования и перегруженный оператор присваивания с помощью метода CopyTree создают новое бинарное дерево поиска для текущего объекта. Алгоритм функции CopyTree был разработан нами для класса TreeNode в разделе, посвященном алгоритмам прохождения деревьев. В том же разделе мы рассматривали алгоритм удаления узлов дерева, который реализован в классе BinSTree функцией DeleteTree и используется как деструктором, так и методом ClearList.

Перегружаемый оператор присваивания копирует объект, стоящий справа, в текущий объект. После проверки того, что объект не присваивается самому себе, функция очищает текущее дерево и с помощью CopyTree создает дубликат присваиваемого дерева (rhs). Указателю current присваивается указатель root, копируется размер списка и возвращается ссылка на текущий объект.

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

template <class T>

BinSTree<T>& BinSTree<T>::operator = (const BinSTree<T>& rhs)

{

// Нельзя копировать дерево в само себя

if (this == &rhs)

return *this;

// Очистить текущее дерево.

//Скопировать новое дерево в текущий объект

ClearList();

root = CopyTree(tree.root);

// Присвоить текущему указателю значение корня и задать

// размер дерева

current = root;

size = tree.size;

// Возвратить ссылку на текущий объект

return *this;

}

Операции обработки списков

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

Операция Find (поиск)

Операция Find использует закрытый элемент-функцию FindNode, принимающую в качестве параметра ключ и осуществляющую прохождение вниз по дереву. Операция возвращает указатель на совпавший узел и указатель на его родителя. Если совпадение происходит в корневом узле, указатель на родителя равен NULL.

Рис. 13.

// Искать элемент данных на дереве. Если найден, возвратить

// адрес совпавшего узла и указатель на его родителя.

// Иначе возвратить NULL

template <class T>

TreeNode<T> *BinSTree<T>::FindNode(const T& item,

TreeNode<T>* & parent) const

{

// Пробежать по узлам дерева, начиная с корня

TreeNode<T> *t = root;

// У корня нет родителя

parent = NULL;

// Прерваться на пустом дереве

while (t !== NULL)

{

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

if (item == t->data)

break;

else

{

// Обновить родительский указатель

// и идти направо или налево

parent = t;

if (item < t->data)

t = t->left;

else

t = t->right;

}

}

// Возвратить указатель на узел; NULL, если не найден

return t;

}

Информация о родителе используется операцией Delete (удаление). В методе Find нас интересует только установление текущей позиции на совпавший узел и присвоение ссылки на этот узел параметру item. Метод Find возвращает True (1) показывая тем самым, что поиск удался, или False (0) — в противном случае. Для сравнения данных в узлах методу Find требуются операторы отношения "==" и "<". Эти операторы должны быть перегруженными, если они не определены для этого базового типа данных.

//Искать item. Если найден, присвоить данные узла параметру item

template <class T>

int BinSTree<T>::Find(T& item)

{

//Используем FindNode, который принимает параметр parent

TreeNode<T> *parent;

// Искать item. Назначить совпавший узел текущим

current = FindNode (item, parent);

// Если найден, скопировать данные уз-ла и возвратить True

if (current != NULL)

{

item = current->data;

return 1;

}

else

// item не найден. Возвратить False

return 0;

}

Операция Insert (вставка)

Метод Insert принимает в качестве параметра новый элемент данных и вставляет его в подходящее место на дереве. Эта функция итеративно проходит путь вдоль левых и правых поддеревьев, пока не найдет точку вставки. На каждом шаге этого пути алгоритм сохраняет запись текущего узла (t) и родителя этого узла (parent). Процесс прекращается по достижении пустого поддерева (t == NULL), которое показывает, что мы нашли место для вставки нового элемента. В этом месте новый узел вставляется в качестве сына данного родителя. Например, следующие шаги вставляют число 32 в дерево, изображенное на Рис. 12.

Метод начинает работу в корневом узле и сравнивает 32 с корневым значением 25 [Рис. 13 (A)]. Поскольку 32 > 25, переходим к правому поддереву и рассматриваем узел 35. t = узел 35; parent = узел 25

Считая узел 35 корнем своего собственного поддерева, сравниваем 32 и 35 и переходим к левому поддереву узла 35 [Рис. 13 (B)].

t = NULL; parent = 35

С помощью GetTreeNode мы создаём листовой узел, содержащий значение 32, а затем вставляем новый узел в качестве левого сына узла 35 [Рис. 12 (C)]:

//Присвоение left возможно, т.к. BinSTree

// является дружественным по отношению к TreeNode

newNode = GetTreeNode(item, NULL, NULL);

parent->left = newNode;

Указатели parent и t являются локальными переменными, изменяющимися по мере нашего продвижения по пути в поисках точки вставки.

// вставить item в дерево поиска

template <class T>

void BinSTree<T>::Insert(const T& item)

{

// t - текущий узел, parent - предыдущий узел

TreeNode<T> *t = root, *parent = NULL, *newNode;

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

while(t != NULL)

{

// обновить указатель parent и идти направо или налево

parent = t;

if (item < t->data)

t = t->left;

else

t = t->right;

}

// если родителя нет, вставить в качестве корневого узла

if (parent == NULL)

root = newNode;

// Если item меньше родительского узла,

//вставить в качестве левого сына

else if (item < parent->data)

parent->left = newNode;

else

// если item больше или равен родительскому узлу,

// вставить в качестве правого сына

parent->right = newNode;

// присвоить указателю current адрес нового узла

//и увеличить size на единицу

current = newNode;

size++;

}

Операция Delete (удаление)

Операция Delete удаляет из дерева узел с заданным ключом. Сначала с помощью метода FindNode устанавливается место этого узла на дереве и определяется указатель на его родителя. Если искомый узел отсутствует, операция удаления завершается.

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

Функция Findnode возвращает указатель DNodePtr на узел D, подлежащий удалению. Второй указатель, PNodePtr, идентифицирует узел P – родителя удаляемого узла. Метод Delete "пытается" подыскать заменяющий узел R, который будет присоединен к родителю и, следовательно, займет место удаленного узла. Заменяющий узел R идентифицируется указателем RNodePtr.

Алгоритм поиска заменяющего узла должен рассмотреть четыре случая, зависящие от числа сыновей удаляемого узла. Заметьте, что если указатель на родителя равен NULL, то удаляется корень. Эта ситуация учитывается нашими четырьмя случаями и тем дополнительным фактором, что корень должен быть обновлен. Поскольку класс BinSTree является другом класса TreeNode, у нас есть доступ к закрытым элементам left и right.

Ситуация A: Узел D не имеет сыновей, т.е. является листом.

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

Обновление совершается путем установки RNodePtr в NULL. Когда мы присоединяем NULL-узел, родитель указывает на NULL.

RNodePtr = DNodePtr->left;

. . .

PNodePtr->right = RNodePtr;

Ситуация B: Узел D имеет только левого сына.

Присоединить левое поддерево узла D к его родителю.

Обновление совершается путем присвоения указателя на левого сына узла D в RNodePtr и последующего присоединения узла R к родителю.

RNodePtr = DNodePtr->right;

. . .

PNodePtr->left = RNodePtr;

Ситуация C: Узел D имеет правого сына, но не имеет левого сына.

Присоединить правое поддерево узла D к его родителю.

Как и в ситуации B, обновление может быть совершено путем присвоения RNodePtr указателя на правого сына узла D и последующего присоединения узла R к родителю.

RNodePtr = DNodePtr->right;

. . .

PNodePtr->left = RNodePtr;

Ситуация D: Удаление узла с двумя сыновьями.

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

Удалив узел 30, мы создали два "осиротевших" поддерева, которые должны быть вновь присоединены к дереву. Для этого требуется стратегия выбора заменяющего узла из оставшейся совокупности узлов. Результирующее дерево должно удовлетворять определению бинарного дерева поиска. Применим max-min-принцип.

Выберите в качестве заменяющего самый правый узел левого поддерева. Это — максимальный из узлов, меньших, чем удаляемый. Отсоедините этот узел (R) от дерева, присоедините его левое поддерево к его родителю в качестве правого поддерева, а затем поставьте R на место удаляемого узла. В демонстрационном дереве заменяющим является узел 28. Мы присоединяем его левого сына (26) к его родителю (25) и заменяем удаленный узел (30) заменяющим (28).

Для отыскания самого правого узла левого поддерева используется следующий простой алгоритм.

Шаг 1: Поскольку заменяющий узел R меньше, чем удаляемый узел D, перейти к левому поддереву узла D. Спуститься к узлу 25.

Шаг 2: Поскольку R является максимальным узлом левого поддерева, найти его значение, спустившись вниз по правому поддереву. Во время спуска следите за предшествующим узлом PofRNodePtr. В нашем примере спуститесь к узлу 28. PofRNodePtr указывает на узел 25.

Спуск вниз по правому поддереву предполагает два случая.

1. Если правое поддерево пусто, то текущая точка — заменяющий узел R и PofRNodePtr указывает на удаляемый узел D. Мы присоединяем правое поддерево узла D в качестве правого поддерева узла R, а родителя (P) удаляемого узла присоединяем к R.

RNodePtr->right = DNodePtr->right;

PNodePtr->left = RNodePtr;

2. Если правое поддерево непусто, мы идем до листового узла или узла, имеющего только левое поддерево.

В любом случае от дерева необходимо отсоединить узел R и присоединить сыновей узла R к родительскому узлу PofRNodePtr. В каждом случае правый сын узла PofRNodePtr переустанавливается оператором

(**) PofRNodePtr->right = PofR-NodePtr->left;

1. R является листом (см. рисунок выше). Отсоединить его от дерева. Поскольку RNodePtr->left равен NULL, оператор (**) устанавливает правого сына узла PofRNodePtr в NULL.

2. R имеет левое поддерево. Оператор (**) присоединяет это поддерево в качестве правого сына узла PofRNodePtr.

Алгоритм заканчивается заменой удаляемого узла узлом R. Сначала сыновья узла D присоединяются в качестве сыновей узла R. Затем узел R замещает узел D как корень поддерева, образованного узлом D.

(**) PofRNodePtr->right = PofRNodePtr->left;

Завершите присоединение к родительскому узлу (Р).

// удаление корневого узла.

// назначение нового корня

if (PNodePtr == NULL)

root = RNodePtr;

// присоединить R к P с правильной стороны

else if (DNodePtr->data < PNodePtr->data)

PNodePtr->left = RNodePtr;

else

PNodePtr->right = RNodePtr;

Альтернативным способом замены узла D на узел R является копирование R в D. Однако, если данные занимают много места в памяти, это может быть дорогостоящей операцией. Наш способ предусматривает изменение лишь двух указателей.

Метод Delete

// если элемент находится на дереве, удалить его

template <class T>

void BinSTree<T>::Delete(const T& item)

{

// DNodePtr - указатель на удаляемый узел D

// PNodePtr - указатель на родительский узел P узла D

// RNodePtr - указатель на узел R, замещающий узел D

TreeNode<T> *DNodePtr, *PNodePtr, *RNodePtr;

// найти узел, данные в котором совпадают с item.

// получить его адрес и адрес его родителя

if ((DNodePtr = FindNode (item, PNodePtr)) == NULL

return;

// если узел D имеет NULL-указатель, то заменяющим

// узлом является тот, что находится на другой ветви

if (DNodePtr->right == NULL)

RNodePtr = DNodePtr->left;

else if (DNodePtr->left == NULL)

RNodePtr = DNodePtr->right;

// узел D имеет двух сыновей

else

{

// найти и отсоединить заменяющий узел R для узла D.

// в левом поддереве узла D найти максимальный узел

// из всех узлов, меньших чем узел D.

// отсоединить этот узел от дерева.

// PofRNodePtr - указатель на родителя заменяющего узла

TreeNode<T> *PofRNodePtr = DNodePtr;

// первой возможной заменой является левый сын узла D

RNodePtr = DNodePtr->left;

//спуститься вниз по правому поддереву левого сына узла D,

// сохраняя указатель на текущий узел и его родителя.

// остановившись, мы будем иметь заменяющий узел

while (RNodePtr->right != NULL)

{

PofRNodePtr = RNodePtr;

RNodePtr = RNodePtr->right;

}

if (PofRNodePtr == DNodePtr)

// левый сын удаляемого узла является заменяющим

// присоединить правое поддерево узла D к узлу R

RNodePtr->right = DNodePtr->right;

else

{

// мы спустились вниз по правой ветви как минимум на один узел.

// удалить заменяющий узел из дерева,

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

PofRNodePtr->right = RNodePtr->left;

}

}

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

// удалить корневой узел. назначить новый корень.

if (RNodePtr == NULL)

root = RNodePtr;

// присоединить узел R к узлу P с правильной стороны

else if (DNodePtr->data < PNodePtr->data)

PNodePtr->left = RNodePtr;

else

PNodePtr->right = RNodePtr;

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

FreeTreeNode(DNodePtr);

size—;

}

Метод Update

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

// Если текущий узел определен и элемент данных (item) совпал

// с данными в этом узле, скопировать элемент данных в узел.

// Иначе вставить item в дерево

template <class T>

void BinSTree<T>::Update( const T& item)

{

if (current != NULL && current->data == item)

current->data = item;

else

Insert(item);

}

Соседние файлы в папке Лабораторная работа4 Деревья