Оценка сбалансированных АВЛ - деревьев. 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Оценка сбалансированных АВЛ - деревьев.



Обоснованность применения АВЛ - деревьев неоднозначна, поскольку они требуют дополнительных затрат на поддержание сбалансированности при вставке или удалении узлов. Если в дереве постоянно происходят вставки и удаления элементов, эти операции могут значительно снизить быстродействие.

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

Для АВЛ - дерева не существует наихудшего случая, так как оно является почти полным бинарным деревом. Сложность операции поиска составляет O(log2n). Опыт показывает, что повороты требуются примерно в половине случаев вставок и удалений. Сложность балансировки обусловливает применение АВЛ - деревьев только там, где поиск является доминирующей операцией.

 


Оценка производительности АВЛ – деревьев.

Эта программа сравнивает сбалансированное и обычное бинарные деревья поиска, каждое из которых содержит N случайных чисел. Исходные данные для этих деревьев берутся из единого массива. Для каждого элемента массива осуществляется его поиск в обоих деревьях. Длины поисковых путей суммируются, а затем подсчитывается средняя длина поиска по каждому дереву. Программа прогоняется на 1000- и на 10000-элементном массивах.

Обратите внимание, что на случайных данных поисковые характеристики АВЛ - дерева несколько лучше. В самом худшем случае вырожденное дерево поиска, содержащее 1000 элементов, имеет среднюю глубину 500, в то время как средняя глубина АВЛ - дерева всегда равна 9.

 

#include <iostream.h>

#include "bstree.h"

#include "avltree.h"

#include "random.h"

 

// загрузить из массива числа в бинарное поисковое дерево и АВЛ – дерево

 

void SetupLists(BinSTree<int> &Tree1, AVLTree<int> &Tree2, int A[], int n)

{

 int i;

 RandomNumber rnd;

 

 // запомнить случайное число в массиве А, а также вставить его в бинарное дерево поиска и в АВЛ - дерево

 for (i=0; i<n; i++)

 {

 A[i] = rnd.Random(1000);

 Tree1.Insert(A[i]);

 Tree2.Insert(A[i]);

}

 

// поиск элемента item в дереве t

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

 

template <class T>

 

void PathLength(TreeNode<T> *t, long &totallength, int item)

{

 // возврат, если элемент найден или отсутствует в списке

 if (t == NULL || t->data == item)

 return;

 else

 {

 // перейти на следующий уровень, увеличить суммарную длину пути поиска

 totallength++;

 if (item < t->data)

 PathLength(t->Left(), totallength, item);

 else

 PathLength(t->Right(), totallength, item);

}

 

void main(void);

{

 // переменные для деревьев и массива

 BinSTree<int> binTree;

 AVLTree<int> avlTree;

 int *A;

 

 // суммарные длины поисковых путей элементов массива в бинарном дереве поиска и в АВЛ - дереве

 long totalLengthBintree = 0, totalLengthAVLTree = 0;

 int n, i;

 cout << "Сколько узлов на дереве? ";

 cin >> n;

 

 // загрузить случайными числами массив и оба дерева

 SetupLists(binTree, avlTree, A, n);

 

 for (i=0; i<n; i++)

 {

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

 PathLength((TreeNode<int> *)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 <iostreani.h>

#include <stdlib.h>

 

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 используют прямой доступ к вершине стека и не зависят от количества элементов в списке.



Поделиться:


Последнее изменение этой страницы: 2020-03-14; просмотров: 189; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.143.9.115 (0.037 с.)