======166
Так как АВЛ‑деревья являются особым типом упорядоченных деревьев, то для них нужно выполнить те же самые шаги. Тем не менее, после их завершения необходимо вернуться назад по дереву, чтобы убедиться в том, что оно осталось сбалансированным. Если найдется узел, для которого не выполняется свойство АВЛ‑деревьев, то нужно выполнить для балансировки дерева соответствующее вращение. Хотя это те же самые вращения, которые использовались раньше для вставки узла в дерево, они применяются в других случаях.
Предположим, что мы удаляем узел из левого поддерева узла X. Также предположим, что правое поддерево либо уравновешено, либо высота его правой половины на единицу больше, чем высота левой. Тогда левое вращение, показанное на рис. 7.12, приведет к балансировке дерева в узле X.
Нижний уровень поддерева T2 закрашен серым цветом, чтобы показать, что поддерево TB либо уравновешено (T2 и T3 имеют одинаковую высоту), либо его правая половина выше (T3 выше, чем T2). Другими словами, закрашенный уровень может существовать в поддереве T2 или отсутствовать.
Если T2 и T3 имеют одинаковую высоту, то высота поддерева TX с корнем в узле X не меняется после удаления узла. Высота TX при этом остается равной высоте поддерева T2 плюс 2. Так как эта высота не меняется, то дерево выше этого узла остается сбалансированным.
Если T3 выше, чем T2, то поддерево TX становится ниже на единицу. В этом случае, дерево может быть несбалансированным выше узла X, поэтому необходимо продолжить проверку дерева, чтобы определить, выполняется ли свойство АВЛ‑деревьев для предков узла X.
Предположим теперь, что узел удаляется из левого поддерева узла X, но левая половина правого поддерева выше, чем правая. Тогда для балансировки дерева нужно использовать вращение вправо‑влево, показанное на рис. 7.13.
Если левое или правое поддеревья T2 или T3 выше, то вращение вправо‑влево приведет к балансировке поддерева TX, и уменьшит при этом высоту TX на единицу. Это значит, что дерево выше узла X может быть несбалансированным, поэтому необходимо продолжить проверку выполнения свойства АВЛ‑деревьев для предков узла X.
@Рис. 7.12. Левое вращение при удалении узла
========167
@Рис. 7.13. Вращение вправо‑влево при удалении узла
Остальные вращения выполняются аналогично. В этом случае удаляемый узел находится в правом поддереве узла X. Эти четыре вращения — те же самые, которые использовались для балансировки дерева при вставке узла, за одним исключением.
Если новый узел вставляется в дерево, то первое выполняемое вращение осуществляет балансировку поддерева TX, не изменяя его высоту. Это значит, что дерево выше узла TX будет при этом оставаться сбалансированным. Если же эти вращения используются после удаления узла из дерева, то вращение может уменьшить высоту поддерева TX на единицу. В этом случае, нельзя быть уверенным, что дерево выше узла X осталось сбалансированным. Нужно продолжить проверку выполнения свойства АВЛ‑деревьев вверх по дереву.
Подпрограмма DeleteItem удаляет элементы из дерева. Она рекурсивно спускается по дереву в поиске удаляемого элемента и когда она находит искомый узел, то удаляет его. Если у этого узла нет потомков, то процедура завершается. Если есть только один потомок, то процедура заменяет узел его потомком.
Если узел имеет двух потомков, процедура DeleteItem вызывает процедуру ReplaceRightMost для замены искомого узла самым правым узлом в его левой ветви. Процедура ReplaceRightMost выполняется примерно так же, как и процедура из 6 главы, которая удаляет элементы из обычного (неупорядоченного) дерева. Основное отличие возникает при возврате из процедуры и рекурсивном проходе вверх по дереву. При этом процедура ReplaceRightMost использует восходящую рекурсию, чтобы убедиться, что дерево остается сбалансированным для всех узлов.
При каждом возврате из процедуры, экземпляр процедуры ReplaceRightMost вызывает подпрограмму RebalanceRightShrunk, чтобы убедиться, что дерево в этой точке сбалансировано. Так как процедура ReplaceRightMost опускается по правой ветви, то она всегда использует для выполнения балансировки подпрограмму RebalanceRightShrunk, а не RebalanceLeftShrunk.
При первом вызове подпрограммы ReplaceRightMost процедура DeleteItem направляет ее по левой от удаляемого узла ветви. При возврате из первого вызова подпрограммы ReplaceRightMost, процедура DeleteItem использует подпрограмму RebalanceLeftShrunk, чтобы убедиться, что дерево сбалансировано в этой точке.
=========168
После этого, один за другим происходят рекурсивные возвраты из процедуры DeleteItem при проходе дерева в обратном направлении. Так же, как и процедура ReplaceRightmost, процедура DeleteItem вызывает подпрограммы RebalanceRightShrunk или RebalanceLeftShrunk в зависимости от того, по какому пути происходит спуск по дереву.
Подпрограмма RebalanceLeftShrunk аналогична подпрограмме RebalanceRightShrunk, поэтому она не показана в следующем коде.
Public Sub DeleteItem(node As AVLNode, txt As String, shrunk As Boolean)
Dim child As AVLNode
Dim target As AVLNode
If node Is Nothing Then
Beep
MsgBox "Элемент " & txt & " не содержится в дереве."
shrunk = False
Exit Sub
End If
If txt < node.Box.Caption Then
Set child = node.LeftChild
DeleteItem child, txt, shrunk
Set node.LeftChild = child
If shrunk Then RebalanceLeftShrunk node, shrunk
ElseIf txt > node.Box.Caption Then
Set child = node.RightChild
DeleteItem child, txt, shrunk
Set node.RightChild = child
If shrunk Then RebalanceRightShrunk node, shrunk
Else
Set target = node
If target.RightChild Is Nothing Then
' Потомков нет или есть только правый.
Set node = target.LeftChild
shrunk = True
ElseIf target.LeftChild Is Nothing Then
' Есть только правый потомок.
Set node = target.RightChild
shrunk = True
Else
' Есть два потомка.
Set child = target.LeftChild
ReplaceRightmost child, shrunk, target
Set target.LeftChild = child
If shrunk Then RebalanceLeftShrunk node, shrunk
End If
End If
End Sub
Private Sub ReplaceRightmost(repl As AVLNode, shrunk As Boolean, target As AVLNode)
Dim child As AVLNode
If repl.RightChild Is Nothing Then
target.Box.Caption = repl.Box.Caption
Set target = repl
Set repl = repl.LeftChild
shrunk = True
Else
Set child = repl.RightChild
ReplaceRightmost child, shrunk, target
Set repl.RightChild = child
If shrunk Then RebalanceRightShrunk repl, shrunk
End If
End Sub
Private Sub RebalanceRightShrunk(node As AVLNode, shrunk As Boolean)
Dim child As AVLNode
Dim child_bal As Integer
Dim grandchild As AVLNode
Dim grandchild_bal As Integer
If node.Balance = RIGHT_HEAVY Then
' Правая часть перевешивала, теперь баланс восстановлен.
node.Balance = BALANCED
ElseIf node.Balance = BALANCED Then
' Было сбалансировано, теперь перевешивает левая часть.
node.Balance = LEFT_HEAVY
shrunk = False
Else
' Левая часть перевешивала, теперь не сбалансировано.
Set child = node.LeftChild
child_bal = child.Balance
If child_bal <= 0 Then
' Правое вращение.
Set node.LeftChild = child.RightChild
Set child.RightChild = node
If child_bal = BALANCED Then
node.Balance = LEFT_HEAVY
child.Balance = RIGHT_HEAVY
shrunk = False
Else
node.Balance = BALANCED
child.Balance = BALANCED
End If
Set node = child
Else
' Вращение влево‑вправо.
Set grandchild = child.RightChild
grandchild_bal = grandchild.Balance
Set child.RightChild = grandchild.LeftChild
Set grandchild.LeftChild = child
Set node.LeftChild = grandchild.RightChild
Set grandchild.RightChild = node
If grandchild_bal = LEFT_HEAVY Then
node.Balance = RIGHT_HEAVY
Else
node.Balance = BALANCED
End If
If grandchild_bal = RIGHT_HEAVY Then
child.Balance = LEFT_HEAVY
Else
child.Balance = BALANCED
End If
Set node = grandchild
grandchild.Balance = BALANCED
End If
End If
End Sub
Программа AVL оперирует АВЛ‑деревом. Введите текст и нажмите на кнопку Add, чтобы добавить элемент к дереву. Введите значение, и нажмите на кнопку Remove, чтобы удалить этот элемент из дерева. На рис. 7.14 показана программа AVL.
Б‑деревья (B‑trees) являются другой формой сбалансированных деревьев, немного более наглядной, чем АВЛ‑деревья. Каждый узел в Б‑дереве может содержать несколько ключей данных и несколько указателей на дочерние узлы. Поскольку каждый узел содержит несколько элементов, такие узлы иногда называются блоками.
=======171
@Рис. 7.14. Программа AVL
Между каждой парой соседних указателей находится ключ, который можно использовать для определения ветви, по которой нужно следовать при вставке или поиске элемента. Например, в дереве, показанном на рис. 7.15, корневой узел содержит два ключа: G и R. Чтобы найти элемент со значением, которое идет перед G, нужно искать в первой ветви. Чтобы найти элемент, имеющий значение между G и R, проверяется вторая ветвь. Чтобы найти элемент, который следует за R, выбирается третья ветвь.
Б‑дерево порядка K обладает следующими свойствами:
· Каждый узел содержит не более 2 * K ключей.
· Каждый узел, кроме может быть корневого, содержит не менее K ключей.
· Внутренний узел, имеющий M ключей, имеет M + 1 дочерних узлов.
· Все листья дерева находятся на одном уровне.
Б‑дерево на рис. 7.15 имеет 2 порядок. Каждый узел может иметь до 4 ключей. Каждый узел, кроме может быть корневого, должен иметь не менее двух ключей. Для удобства, узлы Б‑дерева обычно имеют четное число ключей, поэтому порядок дерева обычно является целым числом.
Выполнение требования, чтобы каждый узел Бдерева порядка K содержал от K до 2 * K ключей, поддерживает дерево сбалансированным. Так как каждый узел должен иметь не менее K ключей, он должен при этом иметь не менее K + 1 дочерних узлов, поэтому дерево не может стать слишком высоким и тонким. Наибольшая высота Б‑дерева, содержащего N узлов, может быть равна O(logK+1(N)). Это означает, что сложность алгоритма поиска в таком дереве порядка O(log(N)). Хотя это и не так очевидно, операции вставки и удаления элемента из Б‑дерева также имеют сложность порядка O(log(N)).
@Рис. 7.15. Б‑дерево
=======172
Применение Б‑деревьев особенно полезно при разработке больших приложений, работающих с базами данных. При достаточно большом порядке Б‑дерева, любой элемент в дереве можно найти после проверки всего нескольких узлов. Например, высота Б‑дерева 10 порядка, содержащего миллион записей, не может быть больше log11(1.000.000), или выше шести уровней. Чтобы найти определенный элемент, потребуется проверить не более шести узлов.