Представление множеств. Деревья. Сбалансированные деревья.
Восстановление сбалансированности требует движения от
листьев к корню, поэтому будем хранить в стеке путь от корня к рассматриваемой в данный момент вершине. Элементами стека будут
пары вершина, направление движения из
нее
, т.е. значения типа
record | vert: 1..n; {вершина} | direction : (l, r); {l - левое, r - правое} end;
Программа добавления элемента t теперь выглядит так:
if root = null then begin | get_free (root); | left[root] := null; right[root] := null; diff[root] := 0; | val[root] := t; end else begin | x := root; ..сделать стек пустым | {инвариант: осталось добавить t к непустому поддереву с | корнем в x; стек содержит путь к x} | while ((t < val [x]) and (left [x] <> null)) or | | ((t > val [x]) and (right [x] <> null)) do begin | | if t < val [x] then begin | | | ..добавить в стек пару <x, l> | | | x := left [x]; | | end else begin {t > val [x]} | | | ..добавить в стек пару <x, r> | | | x := right [x]; | | end; | end; | if t <> val [x] then begin {t нет в дереве} | | get_free (i); val [i] := t; | | left [i] := null; right [i] := null; diff [i] := 0; | | if t < val [x] then begin | | | ..добавить в стек пару <x, l> | | | left [x] := i; | | end else begin {t > val [x]} | | | ..добавить в стек пару <x, r> | | | right [x] := i; | | end; | | d := 1; | | {инвариант: стек содержит путь к изменившемуся | | поддереву, высота которого увеличилась по | | сравнению с высотой в исходном дереве | | на d (=0 или 1); это поддерево сбалансировано; | | значения diff для его вершин правильны; в | | остальном дереве все осталось как было - | | в частности, значения diff} | | while (d <> 0) and ..стек непуст do begin {d = 1} | | | ..взять из стека пару в <v, direct> | | | if direct = l then begin | | | | if diff [v] = 1 then begin | | | | | c := 0; | | | | end else begin | | | | | c := 1; | | | | end; | | | | diff [v] := diff [v] - 1; | | | end else begin | | | {direct = r} | | | | if diff [v] = -1 then begin | | | | | c := 0; | | | | end else begin | | | | | c := 1; | | | | end; | | | | diff [v] := diff [v] + 1; | | | end; | | | {c = изменение высоты поддерева с корнем в v по | | | сравнению с исходным деревом; массив diff | | | содержит правильные значения для этого поддерева; | | | возможно нарушение сбалансированности в v} | | | balance (v, d1); d := c + d1; | | end; | end; end;
Легко проверить, что значение d может быть равно только 0 или 1 (но не -1 ): если c=0, то diff[v]=0 и балансировка не производится.
Программа удаления строится аналогично. Ее основной фрагмент таков:
{инвариант: стек содержит путь к изменившемуся поддереву, высота которого изменилась по сравнению с высотой в исходном дереве на d (=0 или -1); это поддерево сбалансировано; значения diff для его вершин правильны; в остальном дереве все осталось как было - в частности, значения diff} while (d <> 0) and ..стек непуст do begin | {d = -1} | ..взять из стека пару в <v, direct> | if direct = l then begin | | if diff [v] = -1 then begin | | | c := -1; | | end else begin | | | c := 0; | | end; | | diff [v] := diff [v] + 1; | end else begin {direct = r} | | if diff [v] = 1 then begin | | | c := -1; | | end else begin | | | c := 0; | | end; | | diff [v] := diff [v] - 1; | end; | {c = изменение высоты поддерева с корнем в v по | сравнению с исходным деревом; массив diff содержит | правильные значения для этого поддерева; | возможно нарушение сбалансированности в v} | balance (v, d1); | d := c + d1; end;
Легко проверить, что значение d может быть равно только 0 или -1 (но не -2 ): если c=-1, то diff[v]=0 и балансировка не производится.
Отметим также, что наличие стека делает излишними переменные father и direction (их роль теперь играет вершина стека).
14.2.6. Доказать, что при добавлении элемента
(а) второй из трех случаев балансировки (см.рисунок к задаче 14.2.3.) невозможен;
(б) полная балансировка требует не более одного вращения (после чего все дерево становится сбалансированным), в то время как при удалении элемента может понадобиться много вращений.
Замечание. Мы старались записать программы добавления и удаления так, чтобы они были как можно более похожими друг на друга. Используя специфику каждой из них, можно многое упростить.
Существуют и другие способы представления множеств,
гарантирующие число действий порядка на каждую
операцию. Опишем один из них (называемый Б-деревьями ).
До сих пор каждая вершина содержала один элемент хранимого
множества. Этот элемент служил границей между левым
и правым поддеревом. Будем теперь хранить в вершине элементов множества (число
может меняться от вершины к вершине, а также при добавлении и удалении новых элементов, см.далее). Эти
элементов служат
разделителями для
поддерева. Пусть фиксировано
некоторое число
. Будем рассматривать деревья,
обладающие такими свойствами:
- Каждая вершина содержит от
до
элементов (за исключением корня, который может содержать любое число элементов от
до
).
- Вершина с
элементами либо имеет
сына, либо не имеет сыновей вообще (является
).
- Все листья находятся на одной и той же высоте.
Добавление элемента происходит так. Если лист, в который он попадает, неполон (т.е. содержит
менее элементов), то нет проблем. Если он полон, то
элемент (все элементы листа и новый элемент)
разбиваем на два листа по
элементов и разделяющий их
серединный элемент. Этот серединный элемент надо добавить
в вершину предыдущего уровня. Это возможно, если в ней
менее
элементов. Если и она полна, то ее разбивают на
две, выделяют серединный элемент и т.д. Если в конце концов
мы захотим добавить элемент в корень, а он окажется полным,
то корень расщепляется на две вершины, а высота дерева
увеличивается на
.
Удаление элемента, находящегося не в листе, сводится к удалению непосредственно следующего за ним,
который находится в листе. Поэтому достаточно научиться удалять
элемент из листа. Если лист при этом становится слишком маленьким,
то его можно пополнить за счет соседнего листа - если только и он не
имеет минимально возможный размер . Если же оба листа имеют размер
, то на них вместе
элементов, вместе с разделителем -
. После удаления одного элемента остается
элементов - как раз на один лист. Если при этом вершина предыдущего уровня становится меньше нормы, процесс повторяется и т.д.
14.2.7.
Реализовать описанную схему хранения множеств,
убедившись, что она также позволяет обойтись действий для операций включения, исключения и проверки принадлежности.
14.2.8.
Можно определять сбалансированность дерева иначе:
требовать, чтобы для каждой вершины ее левое и правое
поддеревья имели не слишком сильно отличающиеся количества
вершин. (Преимущество такого определения состоит в том, что
при вращениях не нарушается сбалансированность в вершинах,
находящихся ниже точки вращения.) Реализовать на основе
этой идеи способ хранения множеств, гарантирующий оценку
в действий для включения, удаления и проверки
принадлежности.
Указание. Он также использует большие и малые вращения. Подробности см.в книге Рейнгольда, Нивергельта и Део "Комбинаторные алгоритмы".