Разбираемся в работе с АВЛ деревями на C#

Приветствую всех, тема довольно сложна для понимания, и требует вашей концентрации внимания.

АВЛ-дерево – сбалансированное по высоте двоичное дерево поиска. Было названо в честь советских учёных Адельсона-Вельского Георгия Максимовича и Ландиса Евгения  Михайловича, которые впервые описали алгоритм и его структуру. Правила построения двоичных деревьев поиска:

  • каждый узел может иметь не более двух потомков (левый и правый);
  • значения которые меньше текущего размещаются в левом поддереве;
  • значения которые больше или равные текущему размещаются в правом поддереве;

Дополнительное условие для АВЛ-дерева:

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

Узел «6» не имеет родительского узла, поэтому он является корнем дерева и находится на первом уровне. Узлы «4» и «8» это братские узлы, которые находятся на втором уровне. Узлы «3» и «5» находятся на третьем уровне. Используя эти уровни, можно найти расстояние между узлами «6» и «3», оно равно двум.

Не сбалансированное дерево

Минимальная высота левого и правого поддерева не может отличатся больше чем на единицу и это правило характерно для всех узлов дерева. Если рассмотреть корень узла, то его левое поддерево имеет высоту два, а правое – ноль, поэтому дерево не сбалансировано.

Частично сбалансированное дерево

Дерево называется полностью сбалансированным, если каждый узел дерева сбалансированный. Узел «7» не сбалансированный, поскольку высота правого поддерева два, а левого – ноль.

Алгоритм балансировки

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

  • правое вращение,
  • левое вращение,
  • два смешанных алгоритма, которые базируются на первых двух.

Существуют четыре типа вращения:

  • вращение влево,
  • вращение вправо,
  • вращение влево,
  • а потом в право и в обратной последовательности.

Вращение узла вправо

Алгоритм вращения вправо имеет три шага:

  • земенить текущий корень на его левого потомка; узел B становится корнем, а узел А занимает его место.
  • переместить правого потомка нового корня на место левого потомка старого корня. B -> C to D -> C.
  • Присвоить новому корню (B) в качестве правого потомка старый корень (D).

Вращение узла влево

Алгоритм вращения влево осуществляется в три шага:

  • земенить текущий корень на его правого потомка; узел «С» становится корнем, а узел «А» его левым потомком.
  • переместить левого потомка нового корня (B) на место правого потомка старого корня. «С» -> «B» to «A» -> «B».
  • Присвоить новому корню (С) в качестве правого узла значение старого корня (D).

Вращение вправо и влево

Данная ситуация отличается от предыдущих, поскольку правый потомок корня имеет левого потомка, но не имеет правого.

Вращение влево и вправо

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

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AVLTreeNode
{

    // Класс AVLTreeNode реализует один узел АВЛ дерева 

    public class AVLTreeNode<TNode> : IComparable<TNode> where TNode : IComparable
    {
        AVLTree<TNode> _tree;

        AVLTreeNode<TNode> _left;   // левый  потомок
        AVLTreeNode<TNode> _right;  // правый потомок


        #region Конструктор
        public AVLTreeNode(TNode value, AVLTreeNode<TNode> parent, AVLTree<TNode> tree)
        {
            Value = value;
            Parent = parent;
            _tree = tree;
        }
        #endregion

        #region Свойства 
        public AVLTreeNode<TNode> Left
        {
            get
            {
                return _left;
            }

            internal set
            {
                _left = value;

                if (_left != null)
                {
                    _left.Parent = this;  // установка указателя на родительский элемент
                }
            }
        }

        public AVLTreeNode<TNode> Right
        {
            get
            {
                return _right;
            }

            internal set
            {
                _right = value;

                if (_right != null)
                {
                    _right.Parent = this; // установка указателя на родительский элемент
                }
            }
        }

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

        public AVLTreeNode<TNode> Parent
        {
            get;
            internal set;
        }

        // значение текущего узла 

        public TNode Value
        {
            get;
            private set;
        }

        // Сравнивает текущий узел по указаному значению, возвращет 1, если значение экземпляра больше переданного значения,  
        // возвращает -1, когда значение экземпляра меньше переданого значения, 0 - когда они равны.     
        #endregion

        #region CompareTo
        public int CompareTo(TNode other)
        {
            return Value.CompareTo(other);
        }
        #endregion

        #region Balance

        internal void Balance()
        {
            if (State == TreeState.RightHeavy)
            {
                if (Right != null && Right.BalanceFactor < 0)
                {
                    LeftRightRotation();
                }

                else
                {
                    LeftRotation();
                }
            }
            else if (State == TreeState.LeftHeavy)
            {
                if (Left != null && Left.BalanceFactor > 0)
                {
                    RightLeftRotation();
                }
                else
                {
                    RightRotation();
                }
            }
        }
        private int MaxChildHeight(AVLTreeNode<TNode> node)
        {
            if (node != null)
            {
                return 1 + Math.Max(MaxChildHeight(node.Left), MaxChildHeight(node.Right));
            }

            return 0;
        }

        private int LeftHeight
        {
            get
            {
                return MaxChildHeight(Left);
            }
        }

        private int RightHeight
        {
            get
            {
                return MaxChildHeight(Right);
            }
        }

        private TreeState State
        {
            get
            {
                if (LeftHeight - RightHeight > 1)
                {
                    return TreeState.LeftHeavy;
                }

                if (RightHeight - LeftHeight > 1)
                {
                    return TreeState.RightHeavy;
                }

                return TreeState.Balanced;
            }
        }


        private int BalanceFactor
        {
            get
            {
                return RightHeight - LeftHeight;
            }
        }

        enum TreeState
        {
            Balanced,
            LeftHeavy,
            RightHeavy,
        }

        #endregion

        #region LeftRotation

        private void LeftRotation()
        {

            // До
            //     12(this)     
            //      \     
            //       15     
            //        \     
            //         25     
            //     
            // После     
            //       15     
            //      / \     
            //     12  25  

            // Сделать правого потомка новым корнем дерева.
            AVLTreeNode<TNode> newRoot = Right;
            ReplaceRoot(newRoot);
            
            // Поставить на место правого потомка - левого потомка нового корня.    
            Right = newRoot.Left;
            // Сделать текущий узел - левым потомком нового корня.    
            newRoot.Left = this;
        }

        #endregion

        #region RightRotation
        
        private void RightRotation()
        {     
            // Было
            //     c (this)     
            //    /     
            //   b     
            //  /     
            // a     
            //     
            // Стало    
            //       b     
            //      / \     
            //     a   c  
            
            // Левый узел текущего элемента становится новым корнем
            AVLTreeNode<TNode> newRoot = Left;
            ReplaceRoot(newRoot);  
            
            // Перемещение правого потомка нового корня на место левого потомка старого корня
            Left = newRoot.Right;  
            
            // Правым потомком нового корня, становится старый корень.     
            newRoot.Right = this; 
        }

        #endregion

        #region LeftRightRotation

        private void LeftRightRotation() 
        { 
            Right.RightRotation(); 
            LeftRotation(); 
        }
        #endregion

        #region RightLeftRotation

        private void RightLeftRotation() 
        { 
            Left.LeftRotation();
            RightRotation(); 
        }
        #endregion

        #region Перемещение корня

        private void ReplaceRoot(AVLTreeNode<TNode> newRoot)
        {
            if (this.Parent != null)
            {
                if (this.Parent.Left == this)
                {
                    this.Parent.Left = newRoot;
                }
                else if (this.Parent.Right == this)
                {
                    this.Parent.Right = newRoot;
                }
            }
            else
            {
                _tree.Head = newRoot;
            }

            newRoot.Parent = this.Parent;
            this.Parent = newRoot;
        }

        #endregion

    }
}

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AVLTreeNode
{    
    public class AVLTree<T> : IEnumerable<T> where T : IComparable
    
    {        
        // Свойство для корня дерева

        public AVLTreeNode<T> Head
        {
            get;
            internal set;
        }
        
        #region Количество узлов дерева
        public int Count
        {
            get;
            private set;
        }
        #endregion

        #region Метод Add

        // Метод добавлет новый узел
 
        public void Add(T value)
        {
            // Вариант 1:  Дерево пустое - создание корня дерева      
            if (Head == null)
            {
                Head = new AVLTreeNode<T>(value, null, this);
            }

            // Вариант 2: Дерево не пустое - найти место для добавление нового узла.

            else
            {
                AddTo(Head, value);
            }

            Count++;
        }

        // Алгоритм рекурсивного добавления нового узла в дерево.
         
        private void AddTo(AVLTreeNode<T> node, T value)
        {
            // Вариант 1: Добавление нового узла в дерево. Значение добавлемого узла меньше чем значение текущего узла.      

            if (value.CompareTo(node.Value) < 0)
            {
                //Создание левого узла, если его нет.

                if (node.Left == null)
                {
                    node.Left = new AVLTreeNode<T>(value, node, this);
                }

                else
                {
                    // Переходим к следующему левому узлу
                    AddTo(node.Left, value);
                }
            }
            // Вариант 2: Добавлемое значение больше или равно текущему значению.
     
            else
            {
                //Создание правого узла, если его нет.         
                if (node.Right == null)
                {
                    node.Right = new AVLTreeNode<T>(value, node, this);
                }
                else
                {
                    // Переход к следующему правому узлу.             
                    AddTo(node.Right, value);
                }
            }
            //node.Balance();
        }

        #endregion

        #region Метод Contains

        public bool Contains(T value) 
              { 
                return Find(value) != null; 
              }
        
        /// <summary> 
        /// Находит и возвращает первый узел который содержит искомое значение.
        /// Если значение не найдено, возвращает null. 
        /// Так же возвращает родительский узел.
        /// </summary> /// 
        /// <param name="value">Значение поиска</param> 
        /// <param name="parent">Родительский элемент для найденного значения/// </param> 
        /// <returns> Найденный узел (или ноль) /// </returns> 

        private AVLTreeNode<T> Find(T value)
        {
                 
            AVLTreeNode<T> current = Head; // помещаем текущий элемент в корень дерева

            // Пока текщий узел на пустой 
            while (current != null)
            {
                int result = current.CompareTo(value); // сравнение значения текущего элемента с искомым значением

                if (result > 0)
                {
                    // Если значение меньшне текущего - переход влево 
                    current = current.Left;
                }
                else if (result < 0)
                {
                    // Если значение больше текщего - переход вправо             
                    current = current.Right;
                }
                else
                {
                    // Элемент найден      
                    break;
                }
            }
            return current;
        } 


        #endregion

        #region Метод Remove
        
        public bool Remove(T value)
        {
            AVLTreeNode<T> current;
            current = Find(value); // находим узел с удаляемым значением
            
            if (current == null) // узел не найден
            {
                return false;
            }

            AVLTreeNode<T> treeToBalance = current.Parent; // баланс дерева относительно узла родителя
            Count--;                                       // уменьшение колиества узлов

            // Вариант 1: Если удаляемый узел не имеет правого потомка      

            if (current.Right == null) // если нет правого потомка
            {
                if (current.Parent == null) // удаляемый узел является корнем
                {
                    Head = current.Left;    // на место корня перемещаем левого потомка

                    if (Head != null)        
                    {
                        Head.Parent = null; // убераем ссылку на родителя  
                    }
                }
                else // удаляемый узел не является корнем
                {
                    int result = current.Parent.CompareTo(current.Value);

                    if (result > 0)
                    {                        
                        // Если значение родительского узла больше значения удаляемого,
                        // сделать левого потомка удаляемого узла, левым потомком родителя.  

                        current.Parent.Left = current.Left;
                    }
                    else if (result < 0)
                    {
                        
                        // Если значение родительского узла меньше чем удаляемого,                 
                        // сделать левого потомка удаляемого узла - правым потомком родительского узла.                 

                        current.Parent.Right = current.Left;
                    }
                }
            }

            // Вариант 2: Если правый потомок удаляемого узла не имеет левого потомка, тогда правый потомок удаляемого узла
            // становится потомком родительского узла.      

            else if (current.Right.Left == null) // если у правого потомка нет левого потомка
            {
                current.Right.Left = current.Left; 

                if (current.Parent == null) // текущий элемент является корнем
                {
                    Head = current.Right;

                    if (Head != null)
                    {
                        Head.Parent = null;
                    }
                }
                else
                {
                    int result = current.Parent.CompareTo(current.Value); 
                    if (result > 0)
                    {
                        // Если значение узла родителя больше чем значение удаляемого узла,                 
                        // сделать правого потомка удаляемого узла, левым потомком его родителя.                 

                        current.Parent.Left = current.Right;
                    }

                    else if (result < 0)
                    {
                        // Если значение родительского узла меньше значения удаляемого,                 
                        // сделать правого потомка удаляемого узла - правым потомком родителя.                 

                        current.Parent.Right = current.Right;
                    }
                }
            }

            // Вариант 3: Если правый потомок удаляемого узла имеет левого потомка,      
            // заместить удаляемый узел, крайним левым потомком правого потомка.     
            else
            {
                // Нахожление крайнего левого узла для правого потомка удаляемого узла.       
  
                AVLTreeNode<T> leftmost = current.Right.Left;

                while (leftmost.Left != null)
                {
                    leftmost = leftmost.Left;
                }

                // Родительское правое поддерево становится родительским левым поддеревом.         
                
                leftmost.Parent.Left = leftmost.Right;
                
                // Присвоить крайнему левому узлу, ссылки на правого и левого потомка удаляемого узла.         
                leftmost.Left = current.Left;
                leftmost.Right = current.Right;

                if (current.Parent == null)
                {
                    Head = leftmost;

                    if (Head != null)
                    {
                        Head.Parent = null;
                    }
                }
                else
                {
                    int result = current.Parent.CompareTo(current.Value);

                    if (result > 0)
                    {
                        // Если значение родительского узла больше значения удаляемого,                 
                        // сделать крайнего левого потомка левым потомком родителя удаляемого узла.                 

                        current.Parent.Left = leftmost;
                    }
                    else if (result < 0)
                    {
                        // Если значение родительского узла, меньше чем значение удаляемого,                 
                        // сделать крайнего левого потомка, правым потомком родителя удаляемого узла.                 

                        current.Parent.Right = leftmost;
                    }
                }
            }

            if (treeToBalance != null)
            {
                treeToBalance.Balance();
            }

            else
            {
                if (Head != null)
                {
                    Head.Balance();
                }
            }

            return true;

        } 

        #endregion

        #region Метод Clear

        public void Clear() 
        { 
             Head = null; // удаление дерева
             Count = 0; 
        } 

        #endregion

        #region Итераторы

        public IEnumerator<T> InOrderTraversal()
        {
            
            // рекурсивное перемищение по дереву

            if (Head != null) // существует ли корень дерева
            {
                
                Stack<AVLTreeNode<T>> stack = new Stack<AVLTreeNode<T>>();
                AVLTreeNode<T> current = Head;

                // при рекурсивном перемещении по дереву, нужно указывать какой потомок будет слудеющим (правый или левый)
                                
                bool goLeftNext = true;

                // Начинаем с помещения корня в стек
                stack.Push(current);
                
                while (stack.Count > 0)
                {
                    // Если перемещаемся влево ... 
                    if (goLeftNext)
                    {
                        // Перемещение всех левых потомков в стек.
                        
                        while (current.Left != null)
                        {
                            stack.Push(current);
                            current = current.Left;
                        }
                    }

                    yield return current.Value;

                    // Если перемещаемся вправо 

                    if (current.Right != null)
                    {
                        current = current.Right;

                        // Идинажды перемещаемся вправо, после чего опять идем влево. 
                        
                        goLeftNext = true;
                    }
                    else
                    {
                        // Если перейти вправо нельзя - извлекаем родительский узел. 

                        current = stack.Pop();
                        goLeftNext = false;
                    }
                }
            }
        }

        public IEnumerator<T> GetEnumerator() 
        {
            return InOrderTraversal();  
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
        {

            return GetEnumerator();

        }

        #endregion


    }    
    
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AVLTreeNode
{
    class Program
    {
        static void Main(string[] args)
        {

            AVLTree<int> Oak = new AVLTree<int>();
                          //                             10                              10                                             
            Oak.Add(10);  //                            /   \                           /   \
            Oak.Add(3);   //                           /     \                         /     \
            Oak.Add(2);   //                          3      12      ====>            3       15
            Oak.Add(4);   //                         / \     / \                     / \      / \
            Oak.Add(12);  //                        2   4  null 15                  2   4    12  25
            Oak.Add(15);  //                                      \              
            Oak.Add(11);  //                                       25
            Oak.Add(25);  //

            Oak.Remove(11);

            foreach (var item in Oak)
            {
                Console.WriteLine(item);
            }

            Console.ReadKey();

        }
    }
}

 

Обновлено: 04.08.2018 — 10:43

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.