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

2016-07-30СтудИзба

Описание файла

Документ из архива "Реализация АВЛ–деревьев через классы объектно–ориентированного программирования", который расположен в категории "". Всё это находится в предмете "информатика" из 1 семестр, которые можно найти в файловом архиве . Не смотря на прямую связь этого архива с , его также можно найти и в других разделах. Архив можно найти в разделе "курсовые/домашние работы", в предмете "информатика, программирование" в общих файлах.

Онлайн просмотр документа "48754"

Текст 4 страницы из документа "48754"

SetupLists(binTree, avlTree, A, n);

for (i=0; i

{

PathLength(binTree.GetRoot(), totalLengthBintree, A[i]);

PathLength((TreeNode *)avlTree.getRoot(),

totalLengthAVLTree, A[i]);

}

cout << "Средняя длина поиска для бинарного дерева = "

<< float(totalLengthBintree)/n << endl;

cout << "Средняя длина поиска для сбалансированного дерева = "

<< float(totalLengthAVLTree)/n << endl;

}

Прогон 1:Сколько узлов на дереве? 1000

Средняя длина поиска для бинарного дерева = 10.256.

Средняя длина поиска для сбалансированного дерева = 7.901.

Прогон 2:Сколько узлов на дереве? 10000

Средняя длина поиска для бинарного дерева = 12.2822.

Средняя длина поиска для сбалансированного дерева = 8.5632.

Итераторы АВЛ - деревьев.

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

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

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

Объявление:

#include

#include

const int MaxStackSize = 50;

class Stack

{

private:

DataType stacklist[MaxStackSize];

int top;

public:

// конструктор; инициализирует вершину

Stack(void);

// операции модификации стека

void Push(const DataType& item);

DataType Pop(void);

void ClearStack(void);

// доступ к стеку

DataType Peek(void) const;

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

int StackEmpty(void) const;

int StackFull(void) const; // для реализации, основанной на массиве

};

Описание:

Данные в стеке имеют тип DataType, который должен определяться с использованием оператора typedef. Пользователь должен проверять, полный ли стек, перед попыткой поместить в него элемент и проверять, не пустой ли стек, перед извлечением данных из него. Если предусловия для операции push или pop не удовлетворяются, печатается сообщение об ошибке и программа завершается. StackEmpty возвращает TRUE, если стек пустой, и FALSE - в противном случае. Используйте StackEmpty, чтобы определить, может ли выполняться операция Pop.

StackFull возвращает TRUE, если стек полный, и FALSE - в противном случае. Используйте StackFull, чтобы определить, может ли выполняться операция Push. ClearStack делает стек пустым, устанавливая top = -1. Этот метод позволяет использовать стек для других целей.

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

Конструктор Stack присваивает индексу top значение -1, что эквивалентно условию пустого стека.

//инициализация вершины стека

Stack::Stack (void) : top(-l)

{ }

Операции стека.

Две основные операции стека вставляют (Push) и удаляют (Pop) элемент из стека. Класс содержит функцию Peek, позволяющую получать данные элемента, находящегося в вершине стека, не удаляя в действительности этот элемент. При помещении элемента в стек, top увеличивается на 1, и новый элемент вставляется в конец массива stacklist. Попытка добавить элемент в полный стек приведет к сообщению об ошибке и завершению программы.

// поместить элемент в стек

void Stack::Push (const DataTypes item)

{

// если стек полный, завершить выполнение программы

if (top == MaxStackSize-1)

{

cerr << "Переполнение стека!" << endl;

exit(l);

}

// увеличить индекс top и копировать item в массив stacklist

top++;

stacklist[top] = item;

}

Операция Pop извлекает элемент из стека, копируя сначала значение из вершины стека в локальную переменную temp и затем увеличивая top на 1. Переменная temp становится возвращаемым значением. Попытка извлечь элемент из пустого стека приводит к сообщению об ошибке и завершению программы.

// взять элемент из стека

DataType Stack::Pop (void)

DataType temp;

// стек пуст, завершить программу

if (top == -1)

{

cerr << "Попытка обращения к пустому стеку! " << end.1;

exit(1);

}

// считать элемент в вершине стека

temp = stacklist[top];

// уменьшить top и возвратить значение из вершины стека

top--;

return temp;

}

Операция Peek в основном дублирует определение Pop с единственным важным исключением. Индекс top не уменьшается, оставляя стек нетронутым.

// возвратить данные в вершине стека

DataType Stack::Peek (void) const

{

// если стек пуст, завершить программу

if (top == -1)

{

cerr << "Попытка считать данные из пустого стека!" << end.1;

exit(l);

}

return stacklist[top];

}

Условия тестирования стека.

Во время своего выполнения операции стека завершают программу при попытках клиента обращаться к стеку неправильно; например, когда мы пытаемся выполнить операцию Peek над пустым стеком. Для защиты целостности стека класс предусматривает операции тестирования состояния стека. Функция StackEmpty проверяет, является ли top равным -1. Если - да, стек пуст и возвращаемое значение - TRUE; иначе возвращаемое значение - FALSE.

// тестирование стека на наличие в нем данных

int Stack::StackEmpty(void) const

{

return top == -1;

}

Функция StackFull проверяет, равен ли top значению MaxStackSize - 1. Если так, то стек заполнен и возвращаемым значением будет TRUE; иначе, возвращаемое значение - FALSE.

// проверка, не переполнен ли стек

int Stack::StackFuli(void) const

{

return top == MaxStackSize-1;

}

Метод ClearStack переустанавливает вершину стека на -1. Это восстанавливает начальное условие, определенное конструктором.

// удалить все элементы из стека

void Stack::ClearStack(void)

{

top = -1;

}

Стековые операции Push и Pop используют прямой доступ к вершине стека и не зависят от количества элементов в списке.

Абстрактный базовый класс Iterator.

Класс Iterator позволяет абстрагироваться от тонкостей реализации алгоритма перебора, что дает независимость от деталей реализации базового класса. Мы определяем абстрактный класс Iterator как шаблон для итераторов списков общего вида.

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

Объявление:

template

class Iterator

{

protected:

// Флаг, показывающий, достиг ли итератор конца списка должен поддерживаться производными классами

int iterationComplete;

public:

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

Iterator(void);

// обязательные методы итератора

virtual void Next(void) = 0;

virtual void Reset(void) = 0;

// методы для выборки/модификации данных

virtual T& Data(void) = 0;

// проверка конца списка

virtual int EndOfList(void) const;

};

Обсуждение:

Итератор является средством прохождения списка. Его основные методы: Reset (установка на первый элемент списка), Next (переход на следующий элемент), EndOfList (определение конца списка). Функция Data осуществляет доступ к данным текущего элемента списка.

Итератор симметричного метода прохождения.

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

Объявление:

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

template

class InorderIterator: public Iterator

{

private:

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

Stack< TreeNode * > S;

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

TreeNode *root, *current;

// сканирование левого поддерева используется функцией Next

TreeNode *GoFarLeft(TreeNode *t);

public:

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

InorderIterator(TreeNode *tree);

// реализации базовых операций прохождения

virtual void Next(void);

virtual void Reset(void);

virtual T& Data(void);

// назначение итератору нового дерева

void SetTree(TreeNode *tree);

};

Описание:

Класс InorderIterator построен по общему для всех итераторов образцу. Метод EndOfList определен в базовом классе Iterator. Конструктор инициализирует базовый класс и с помощью GoFarLeft находит начальный узел сканирования.

Пример:

TreeNode *root; // бинарное дерево

InorderIterator treeiter(root); // присоединить итератор

// распечатать начальный узел сканирования для смешанного прохождения это самый левый узел дерева

cout << treeiter.Data();

// сканирование узлов и печать их значений

for (treeiter.Reset(); !treeiter.EndOfList(); treeiter.Next())

cout << treeiter.Data() << " ";

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

Итерационный симметричный метод прохождения эмулирует рекурсивное сканирование с помощью стека адресов узлов. Начиная с корня, осуществляется спуск вдоль левых поддеревьев. По пути указатель каждого пройденного узла запоминается в стеке. Процесс останавливается на узле с нулевым левым указателем, который становится первым посещаемым узлом в симметричном сканировании. Спуск от узла t и запоминание адресов узлов в стеке выполняет метод GoFarLeft. Вызовом этого метода с t=root ищется первый посещаемый узел (рис. 18).

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

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

template

TreeNode *InorderIterator::GoFArLeft(TreeNode *t)

{

// если t=NULL, вернуть NULL

if (t == NULL)

return NULL;

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

while (t->Left() != NULL)

{

S.Push(t);

t = t->Left();

}

return t;

}

Рис. 18.

После инициализации базового класса конструктор присваивает элементу данных root адрес корня бинарного дерева поиска. Узел для начала симметричного сканирования получается в результате вызова функции GoFarLeft с root в качестве параметра. Это значение запоминается в переменной сurrent.

// инициализировать флаг iterationComplete. Базовый класс сбрасывает его, но

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

template

InorderIterator::InorderIterator(TreeNode *tree):

Iterator(), root(tree)

{

iterationComplete = (root == NULL);

current = GoFarLeft(root);

}

Метод Reset по существу является таким же, как и конструктор, за исключением того, что он очищает стек. Перед первым обращением к Next указатель current уже указывает на первый узел симметричного сканирования. Метод Next работает по следующему алгоритму. Если правая ветвь узла не пуста, перейти к его правому сыну и осуществить спуск по левым ветвям до узла с нулевым левым указателем, попутно запоминая в стеке адреса пройденных узлов.

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

Рис. 19.

template

void InorderIterator::Next(void)

{

// ошибка, если все узлы уже посещались

if (iterationComplete)

{

cerr << "Next: итератор прошел конец списка!" << endl;

exit(1);

}

// current - текущий обрабатываемый узел

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

if (current->Right() != NULL)

current = GoFarLeft(current->Right());

// правого поддерева нет, но в стеке есть другие узлы, подлежащие обработке.

// Вытолкнуть из стека новый текущий адрес, продвинуться вверх по дереву

else if (!S.StackEmpty())

current = S.Pop();

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

else

iterationComplete = 1;

}

Алгоритм TreeSort.

Когда объект типа InorderIterator осуществляет прохождение дерева поиска, узлы проходятся в сортированном порядке и, следовательно, можно построить еще один алгоритм сортировки, называемый TreeSort. Этот алгоритм предполагает, что элементы изначально хранятся в массиве. Поисковое дерево служит фильтром, куда элементы массива копируются в соответствии с алгоритмом вставки в бинарное дерево поиска. Осуществляя симметричное прохождение этого дерева и записывая элементы снова в массив, мы получаем в результате отсортированный список. На рисунке 20 показана сортировка 8 - элементного массива.

#include "bstree.h"

#include "treeiter.h"

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

template

void TreeSort(T arr[], int n)

{

// бинарное дерево поиска, в которое копируется массив

BinSTree sortTree;

int i;

// вставить каждый элемент массива в поисковое дерево

for (i=0; i

sortTree.Insert(arr[i]);

// объявить итератор симметричного прохождения для sortTree

InorderIterator treeSortIter(sortTree.GetRoot());

// выполнить симметричное прохождение дерева

// скопировать каждый элемент снова в массив

i = 0;

while (!treeSortIter.EndOfList())

{

arr[i++] = treeSortIter.Data();

treeSortIter.Next();

}

}

Рис. 20.

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

Программа создана на объектно – ориентированном языке программирования C++ в среде быстрой разработки (RAD) Bolrand C++ Builder 6.0, имеет графический интерфейс.

Текст программы.

#pragma once

template class AvlTree

{

public:

KEY key;

VALUE value;

int balance;

AvlTree* left;

AvlTree* right;

bool empty;//заполнены ключ и значение или нет

AvlTree()

{

empty=true;

left = NULL;

right = NULL;

balance = 0;

}

AvlTree(KEY Key,VALUE Value)

{

empty=false;

key = Key;

value = Value;

left = NULL;

right = NULL;

balance = 0;

}

int add(KEY Key, VALUE Value)//добавление узла, возвращает изменился баланс(1) или нет (0)

{ //при добавлении в правую ветку баланс узла увеличиваю на результат добавления

if (empty) //в левую уменьшаю

{ //после каждого вызова add(...) вызываю TurnAround();

key = Key; //дерево перестраивается пока потомок текущего узла в нужном направлении не будет NULL

value = Value; //потом в этого потомка записываем новые значения

empty=false;

return 0;

}

if (Key == key)

throw CString(L"Уже есть");

int a = balance;

if (Key > key)

{

if (right != NULL)

{

balance += right->add(Key, Value);

TurnAround();

}

else

{

right = new AvlTree(Key, Value);

balance++;

}

}

else

{

if (left != NULL)

{

balance -= left->add(Key, Value);

TurnAround();

}

else

{

left = new AvlTree(Key, Value);

balance--;

}

}

if (balance != a)

{

return 1;

}

else

{

return 0;

}

}

void TurnAround() //нормализация дерева, если баланс не равномерный смещаю(поворачиваю) узел так,

{ //чтобы количество потомков справа и слева не отличалось больше , чем на 1

if (balance == 2)

{

if (right->balance >= 0)

{

KEY tKey = key;

VALUE tValue = value;

key = right->key;

value = right->value;

right->key = tKey;

right->value = tValue;

AvlTree*tNode=right->right;

right->right = right->left;

right->left = left;

left = right;

right = tNode;

balance = left->balance - 1;

left->balance = 1 - left->balance;

}

else

{

KEY tKey = key;

VALUE tValue = value;

key = right->left->key;

value = right->left->value;

right->left->key = tKey;

right->left->value = tValue;

AvlTree* tNode = right->left->right;

right->left->right = right->left->left;

right->left->left = left;

left = right->left;

right->left = tNode;

balance = 0;

right->balance = (left->balance == -1) ? (1) : (0);

left->balance = (left->balance == 1) ? (-1) : (0);

}

}

else

{

if (balance == -2)

{

if (left->balance <= 0)

{

KEY tKey = key;

VALUE tValue = value;

key = left->key;

value = left->value;

left->key = tKey;

left->value = tValue;

AvlTree* tNode = left->left;

left->left = left->right;

left->right = right;

right = left;

left = tNode;

balance = 1 + right->balance;

right->balance = -1 - right->balance;

}

else

{

KEY tKey = key;

VALUE tValue = value;

key = left->right->key;

value = left->right->value;

left->right->key = tKey;

left->right->value = tValue;

AvlTree* tNode = left->right->left;

left->right->left = left->right->right;

left->right->right = right;

right = left->right;

left->right = tNode;

balance = 0;

left->balance = (right->balance == 1) ? (-1) : (0);

right->balance = (right->balance == -1) ? (1) : (0);

}

}

}

}

typename AvlTree* getNode(KEY Key)//поиск узла по ключу

{

AvlTree* node=this;

while (true)

{

if (node == NULL)

throw CString(L"Не найдено");

if (node->key == Key)

return node;

if (node->key < Key)

{

node = node->right;

}

else

{

node = node->left;

}

}

}

int remove(KEY Key,AvlTree*parent=NULL) //удаление узла, перемещаюсь по дереву, по ключу

{ //при прохождении узла опять меняю баланс в зависимости от направления и поворачиваю его TurnAround()

int a = balance; // как дошел до нужного узла перемещаю его , пока оба его потомка не будут NULL

if (key == Key) // и удаляю

{

if (right == NULL && left == NULL)

{

if(parent->left->key==this->key)

{

parent->left=NULL;

}

else

{

parent->right=NULL;

}

return 1;

}

else

{

if (balance >= 0)

{

if (right != NULL)

{

AvlTree* tNode = right;

while (tNode->left != NULL)

{

tNode = tNode->left;

}

KEY tKey = key;

VALUE tValue = value;

key = tNode->key;

value = tNode->value;

tNode->key = tKey;

tNode->value = tValue;

balance -= right->remove(Key,this);

}

}

else

{

if (left != NULL)

{

AvlTree* tNode = left;

while (tNode->right != NULL)

{

tNode = tNode->right;

}

KEY tKey = key;

VALUE tValue = value;

key = tNode->key;

value = tNode->value;

tNode->key = tKey;

tNode->value = tValue;

balance += left->remove(Key,this);

}

}

}

}

else

{

if (Key > key)

{

if (right!=NULL)

{

balance -= right->remove(Key,this);

TurnAround();

}

else

{

throw CString("Не найдено");

}

}

else

{

if (left!=NULL)

{

balance += left->remove(Key,this);

TurnAround();

}

else

{

throw CString("Не найдено");

}

}

}

if (balance != a)

{

return (balance == 0) ? (1) : (0);

}

else

{

return 0;

}

}

~AvlTree()

{

}

};

СПИСОК ЛИТЕРАТУРЫ

1. Каррано Ф.М., Причард Дж.Дж. К26 Абстракция данных и решение задач на С++ - I -. Стены и зеркала, 3-е издание.: Пер.с англ. - М.: Издательский дом «Вильяме», 2003. - 848 с: ил. - Парал. тит. англ.

2. Ж.-Л. Лорьер. Системы искусственного интеллекта. – М.: Мир, 1991.

3. Бабэ Б. Просто и ясно о Borland С++: пер. с англ. – СПб.: Питер, 1997. – 464 с.

4. Ирэ П. Объектно – ориентированное программирование с использованием С++: пер. с англ. К.: НИПФ ДиаСофтЛтд, 1995. – 480 с.

5. Программирование. Учебник под ред. Свердлика А.Н., МО СССР, 1992. – 608 с.

6. Сван Т. Программирование для Windows в Borland С++: пер. с англ. – М.: БИНОМ. – 480 с.

7. Шамис В.А. Borland С++ Builder. Программирование на С++ без проблем. – М.: Нолидж, 1997. – 266 с.

8. Шилдт Г. Теория и практика С++: пер. с англ. – СПб.: BHV – Санкт – Петербург, 1996. – 416 с.

9. http://www.rsdn.ru/article/alg/bintree/avl.xml.

Свежие статьи
Популярно сейчас
Почему делать на заказ в разы дороже, чем купить готовую учебную работу на СтудИзбе? Наши учебные работы продаются каждый год, тогда как большинство заказов выполняются с нуля. Найдите подходящий учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Нет! Мы не выполняем работы на заказ, однако Вы можете попросить что-то выложить в наших социальных сетях.
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
4098
Авторов
на СтудИзбе
667
Средний доход
с одного платного файла
Обучение Подробнее