Представление множеств. Деревья. Сбалансированные деревья.
Восстановление сбалансированности требует движения от листьев к корню, поэтому будем хранить в стеке путь от корня к рассматриваемой в данный момент вершине. Элементами стека будут пары вершина, направление движения из нее , т.е. значения типа
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. Можно определять сбалансированность дерева иначе: требовать, чтобы для каждой вершины ее левое и правое поддеревья имели не слишком сильно отличающиеся количества вершин. (Преимущество такого определения состоит в том, что при вращениях не нарушается сбалансированность в вершинах, находящихся ниже точки вращения.) Реализовать на основе этой идеи способ хранения множеств, гарантирующий оценку в действий для включения, удаления и проверки принадлежности.
Указание. Он также использует большие и малые вращения. Подробности см.в книге Рейнгольда, Нивергельта и Део "Комбинаторные алгоритмы".