Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Пособие часть 1.doc
Скачиваний:
66
Добавлен:
24.09.2019
Размер:
6.98 Mб
Скачать

Алгоритмы вставки и удаления

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

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

Общий алгоритм вставок и удалений состоит из двух шагов.

  1. Выполнить вставку или удаление элемента в соответствии с алгоритмом для обычного бинарного дерева. При включении нового узла как листа определить для него нулевой показатель сбалансированности.

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

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

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

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

В качестве примера приведем реализацию рекурсивного алгоритма вставки в АВЛ-дерево.

Реализация рекурсивного алгоритма вставки в авл-дерево

Дополним уже имеющуюся структуру узла бинарного дерева поиска еще одним полем (назовем его balance), которое будет служить для хранения баланса узла. Вообще-то для хранения баланса узла достаточно всего двух бит, но в реализации на С++ определим для него тип short.

Для реализации алгоритма вставки нам потребуются вспомогательные функции. Это четыре функции , соответствующие большим и малым правым и левым вращениями, и еще одна функция c именем rotate, в которой будет приниматься решение, какое именно из вращений нужно выполнить.

Алгоритм функции rotate основан на анализе балансов. Если значение этого показателя для узла положительно, значит, перевешивает правое поддерево и нужно выполнять правое вращение, и наоборот, при отрицательном значении нужно выполнять левое вращение. Решение о том, большое или малое вращение нужно выполнить, можно принять на основе рисунков 5.8 и 5.11.

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

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

Для реализации этого алгоритма добавим еще один параметр в функцию вставки — изменение высоты поддерева (в листинге это параметр d). При добавлении узла этот параметр будет принимать значение 1 (узел всегда добавляется в пустое поддерево), а при вращениях, наоборот, высота поддерева уменьшается на 1 (т.е. d=-1).

Реализация рекурсивного алгоритма вставки в АВЛ-дерево приводится в листинге 5.4. Кроме функции вставки и всех необходимых вспомогательных функций, приводится функция построения АВЛ-дерева из заданной последовательности и функция нисходящего обхода, которая строит левое скобочное представление полученного дерева (этого вполне достаточно, чтобы убедиться в том, что построенное дерево является сбалансированным).

Листинг 5. Вставка в АВЛ-дерево с восстановлением сбалансированности

#include <iostream.h>

#include <stdlib.h>

typedef int T_key; //тип ключа, может быть любым

typedef char T_data;//тип связанных данных, любой

struct item //структура элемента

{ T_key key; //ключ

T_data data; //связанные данные

};

struct node // структура узла дерева

{item data; //данные типа item

node *left, *right; // указатели на детей

short balance; // показатель сбалансированности

node(item x) // конструктор, вызывается при создании узла

{data=x;left=right=NULL;balance=0;}

};

typedef node* avlbst; //avlbst - avl binary seach tree

//Малое правое вращение:

void SmallRightRotate(avlbst root)

{ avlbst x,y; item t;

x=root; y=x->right;

t=x->data; x->data=y->data; y->data=t;

x->right=y->right;

y->right=y->left; y->left=x->left;

x->left=y;

x->balance= y->balance=0; //изменяем balance для x и y

}

//Большое правое вращение:

void LargeRightRotate(avlbst root)

{ avlbst x,y,z; item t;

x=root; y=x->right; z=y->left;

t=x->data;x->data=z->data; z->data=t;

y->left=z->right;

z->right=z->left; z->left=x->left;

x->left=z;

x->balance=0;

if (z->balance==0) y->balance=z->balance=0;

else

if (z->balance==-1) { y->balance=0; z->balance=1;}

else {z->balance=0; y->balance=-1;}

}

//Малое левое вращение (аналогично малому правому):

void SmallLeftRotate(avlbst root)

{ avlbst x,y; item t;

x=root; y=x->left;

t=x->data; x->data=y->data; y->data=t;

x->left=y->left;

y->left=y->right; y->right=x->right;

x->right=y;

x->balance=y->balance=0;

}

//Большое левое вращение (аналогично большому правому):

void LargeLeftRotate(avlbst root)

{ avlbst x,y,z; item t;

x=root; y=x->left; z=y->right;

t=x->data;x->data=z->data; z->data=t;

y->right=z->left;

z->left=z->right; z->right=x->right;

x->right=z;

x->balance=0;

if (z->balance==0)

y->balance=z->balance=0;

else

if (z->balance==-1)

{ y->balance=0; z->balance=1;}

else

{ z->balance=0; y->balance=-1;}

}

// функция определяет, какое именно вращение нужно

void rotate(avlbst root)

{ if (root->balance==2) //Правое вращение

if (root->right->balance<0) LargeRightRotate(root);

else SmallRightRotate(root);

else //Левое вращение

if (root->left->balance>0) LargeLeftRotate(root);

else SmallLeftRotate(root);

}

bool insertavl_rec(avlbst &root, item x, int &d)

{ //параметр d - изменение высоты текущего поддерева

if (!root) // дерево пусто – терминальная ветвь

{ root=new node(x); d=1;//создали узел-высота увеличилась на 1

if(root)return true; else return false;

}

// рекурсивная ветвь

if (x.key<root->data.key) // нужно двигаться влево

{ insertavl_rec(root->left,x,d); // вставка в левое поддерево

root->balance=root->balance-d; //корректируем balance у отца

if (abs(root->balance)==2) //если отец (root) разбалансирован

{ rotate(root); d--;}// вызываем нужное вращение

} // оно уменьшило высоту дерева

else // нужно двигаться вправо

{ insertavl_rec(root->right,x,d); // вставка в правое поддерево

root->balance=root->balance+d; //пришли справа, d прибавляется

if (abs(root->balance)==2) // при разбалансировке

{ rotate(root); d--;} // выполняем вращение

}

}

// функция-тест строит дерево из примера (рис.5.13)

void randtree(avlbst &root)

{ item x; int a[7]={4,5,7,2,1,3,6}; int d=0;

for (int i=0;i<7; i++)

{ x.key=a[i];

x.data=rand()%26+65; // случайные заглавные латинские буквы

insertavl_rec(root,x,d);

}

}

// вывод дерева в КЛП порядке (для проверки сбалансированности)

void out(avlbst root)

{if (!root) return;

cout<<root->data.key<<' '<<root->data.data<<"; ";

cout<<'('; out(root->left); cout<<")(";

out(root->right);cout<<')';

}