AVL-деревья

AVL-деревья Источник: Технология Клиент-Сервер Бинарные деревья поиска предназначены для быстрого доступа к данным. В идеале разумно сбалансированное дерево имеет высоту порядка O(log2n). Однако при некотором стечении обстоятельств дерево может оказаться вырожденным. Тогда высота его будет O(n), и доступ к данным существенно замедлится. В этом разделе мы рассмотрим модифицированный класс деревьев, обладающих всеми преимуществами бинарных деревьев поиска и никогда не вырождающихся. Они называются сбалансированными или AVL-деревьями. Под сбалансированностью будем понимать то, что для каждого узла дерева высоты обоих его поддеревьев различаются не более чем на 1. Строго говоря, этот критерий нужно называть AVL-сбалансированностью в отличие от идеальной сбалансированности, когда для каждого узла дерева количества узлов в левом и правом поддеревьях различаются не более чем на 1. Здесь мы всегда будем иметь в виду AVL-сбалансированность. Новые методы вставки и удаления в классе AVL-деревьев гарантируют, что все узлы останутся сбалансированными по высоте. На рисунках 1 и 2 показаны эквивалентные представления массива AVL-деревом и бинарным деревом поиска. Рисунок 1 представляет простой пятиэлементный массив А (A[5] = {1,2,3,4,5}), отсортированный по возрастанию. Рисунок 2 представляет массив B (B[8] = {20, 30, 80, 40, 10, 60, 50, 70}). Бинарное дерево поиска имеет высоту 5, в то время как высота AVL-дерева равна 2. В общем случае высота сбалансированного дерева не превышает O(log2n). Таким образом, AVL-дерево является мощной структурой хранения, обеспечивающей быстрый доступ к данным. В этом разделе используется упоминавшийся в предыдущей статье подход, при котором поисковое дерево строится отдельно от своих узлов. Сначала мы разработаем класс AVLTreeNode, а затем используем объекты этого типа для конструирования класса AVLTree. Предметом пристального внимания будут методы Insert и Delete. Они требуют тщательного проектирования, поскольку должны гарантировать, что все узлы нового дерева останутся сбалансированными по высоте. Узлы AVL-дерева AVL-деревья имеют структуру, похожую на бинарные деревья поиска. Все операции идентичны описанным для бинарных деревьев, за исключением методов Insert и Delete, которые должны постоянно отслеживать соотношение высот левого и правого поддеревьев узла. Для сохранения этой информации мы расширили определение объекта TreeNode (см. предыдущий номер), включив поле balanceFactor (показатель сбалансированности), которое содержит разность высот правого и левого поддеревьев. left data balanceFactor right AVLTreeNode balanceFactor = height(right subtree) - height(left subtree) Если balanceFactor отрицателен, то узел «перевешивает влево», так как высота левого поддерева больше, чем высота правого поддерева. При положительном balanceFactor узел «перевешивает вправо». Сбалансированный по высоте узел имеет balanceFactor = 0. В AVL-дереве показатель сбалансированности должен быть в диапазоне [-1, 1]. На рисунке 3 изображены AVL-деревья с пометками -1, 0 и +1 на каждом узле, показывающими относительный размер левого и правого поддеревьев.·-1: Высота левого поддерева на 1 больше высоты правого поддерева. ·0: Высоты обоих поддеревьев одинаковы. ·+1: Высота правого поддерева на 1 больше высоты левого поддерева. clip0069Рис. 1. clip0070Рис. 2. Используя свойства наследования, можно образовать класс AVLTreeNode на базе класса TreeNode. Объект типа AVLTreeNode наследует поля из класса TreeNode и добавляет к ним поле balanceFactor. Данные-члены left и right класса TreeNode являются защищенными, поэтому AVLTreeNode или другие производные классы имеют к ним доступ. clip0071Рис. 3. Спецификация класса AVLTreeNode ОБЪЯВЛЕНИЕ

// наследник класса TreeNode
template < class T >
 class AVLTreeNode: public TreeNode < T >
  {
  private:
  // дополнительный член класса balanceFactor
  // используется методами класса AVLTree и позволяет
  // избегать "перевешивания" узлов
  int balanceFactor;
  AVLTreeNode<T>* amp; Left(void);
  AVLTreeNode<T>* amp; Right(void);
  public:
  // конструктор
  AVLTreeNode(const Tamp; 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.
ПРИМЕРЫ
Эта функция создает AVL-дерево, изображенное на рисунке 4. Каждому узлу присваивается показатель сбалансированности
clip0072Рис. 4.

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

void MakeAVLCharTree(AVLTreeNode < char > * amp; 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 Tamp; item,

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

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

 {}

Методы Left и Right в классе AVLTreeNode упрощают доступ к полям данных. При попытке обратиться к левому сыну с помощью метода Left базового класса возвращается указатель на объект типа TreeNode. Чтобы получить указатель на узел AVL-дерева, требуется преобразование типов. Например,
AVLTreeNode < T > * p, * q;

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

q = (AVLTreeNode < T > * )p - gt;

Left(); // необходимое приведение типа

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

 AVLTreeNodelt;

Tgt;

* AVLTreeNode: : Left(void)

{

  return ((AVLTreeNodelt;Tgt; *)left;

}

Класс AVLTree
AVL-дерево представляет собой списковую структуру, похожую на бинарное дерево поиска, с одним дополнительным условием: дерево должно оставаться сбалансированным по высоте после каждой операции вставки или удаления. Поскольку AVL-дерево является расширенным бинарным деревом поиска, класс AVLTree строится на базе класса BinSTree и является его наследником.
Для выполнения AVL-условий следует изменить методы 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 для восстановления

  // AVL-условий после операций вставки/удаления

  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);

  }
;

Отправить комментарий

Проверка
Антиспам проверка
Image CAPTCHA
...