Приветствую всех, тема довольно сложна для понимания, и требует вашей концентрации внимания.
АВЛ-дерево – сбалансированное по высоте двоичное дерево поиска. Было названо в честь советских учёных Адельсона-Вельского Георгия Максимовича и Ландиса Евгения Михайловича, которые впервые описали алгоритм и его структуру. Правила построения двоичных деревьев поиска:
- каждый узел может иметь не более двух потомков (левый и правый);
- значения которые меньше текущего размещаются в левом поддереве;
- значения которые больше или равные текущему размещаются в правом поддереве;
Дополнительное условие для АВЛ-дерева:
- для любого узла дерева, высота его правого поддерева никогда не превышает высоты его
левого поддерева более чем на единицу.
*Этого свойства достаточно, чтобы высота дерева имела логарифмическую зависимость от
числа его узлов.
Узел «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();
}
}
}
