Опубликован: 08.04.2009 | Доступ: свободный | Студентов: 486 / 0 | Длительность: 17:26:00
Специальности: Программист
Лекция 14:

Представление множеств. Деревья. Сбалансированные деревья.

Восстановление сбалансированности требует движения от листьев к корню, поэтому будем хранить в стеке путь от корня к рассматриваемой в данный момент вершине. Элементами стека будут пары \langle вершина, направление движения из нее \rangle, т.е. значения типа

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.) невозможен;

(б) полная балансировка требует не более одного вращения (после чего все дерево становится сбалансированным), в то время как при удалении элемента может понадобиться много вращений.

Замечание. Мы старались записать программы добавления и удаления так, чтобы они были как можно более похожими друг на друга. Используя специфику каждой из них, можно многое упростить.

Существуют и другие способы представления множеств, гарантирующие число действий порядка log n на каждую операцию. Опишем один из них (называемый Б-деревьями ).

До сих пор каждая вершина содержала один элемент хранимого множества. Этот элемент служил границей между левым и правым поддеревом. Будем теперь хранить в вершине k\ge1 элементов множества (число k может меняться от вершины к вершине, а также при добавлении и удалении новых элементов, см.далее). Эти k элементов служат разделителями для k+1 поддерева. Пусть фиксировано некоторое число t\ge1. Будем рассматривать деревья, обладающие такими свойствами:

  1. Каждая вершина содержит от t до 2t элементов (за исключением корня, который может содержать любое число элементов от 0 до 2t ).
  2. Вершина с k элементами либо имеет k+1 сына, либо не имеет сыновей вообще (является \emph{листом} ).
  3. Все листья находятся на одной и той же высоте.

Добавление элемента происходит так. Если лист, в который он попадает, неполон (т.е. содержит менее 2t элементов), то нет проблем. Если он полон, то 2t+1 элемент (все элементы листа и новый элемент) разбиваем на два листа по t элементов и разделяющий их серединный элемент. Этот серединный элемент надо добавить в вершину предыдущего уровня. Это возможно, если в ней менее 2t элементов. Если и она полна, то ее разбивают на две, выделяют серединный элемент и т.д. Если в конце концов мы захотим добавить элемент в корень, а он окажется полным, то корень расщепляется на две вершины, а высота дерева увеличивается на 1.

Удаление элемента, находящегося не в листе, сводится к удалению непосредственно следующего за ним, который находится в листе. Поэтому достаточно научиться удалять элемент из листа. Если лист при этом становится слишком маленьким, то его можно пополнить за счет соседнего листа - если только и он не имеет минимально возможный размер t. Если же оба листа имеют размер t, то на них вместе 2t элементов, вместе с разделителем - 2t+1. После удаления одного элемента остается 2t элементов - как раз на один лист. Если при этом вершина предыдущего уровня становится меньше нормы, процесс повторяется и т.д.

14.2.7. Реализовать описанную схему хранения множеств, убедившись, что она также позволяет обойтись C \log n действий для операций включения, исключения и проверки принадлежности.

14.2.8. Можно определять сбалансированность дерева иначе: требовать, чтобы для каждой вершины ее левое и правое поддеревья имели не слишком сильно отличающиеся количества вершин. (Преимущество такого определения состоит в том, что при вращениях не нарушается сбалансированность в вершинах, находящихся ниже точки вращения.) Реализовать на основе этой идеи способ хранения множеств, гарантирующий оценку в C \log n действий для включения, удаления и проверки принадлежности.

Указание. Он также использует большие и малые вращения. Подробности см.в книге Рейнгольда, Нивергельта и Део "Комбинаторные алгоритмы".