Можно было бы сопоставить вершины полного двоичного дерева
с числами
(считая, что левый сын n есть 2n ,
правый сын n есть 2n + 1 ) и хранить пометки в массиве
val [1...]. Однако этот способ неэкономен, поскольку
тратится место на хранение пустых вакансий в полном двоичном
дереве.
Более экономен такой способ. Введем три массива
val: array [1..n] of T;
left, right: array [1..n] of 0..n;
(n -- максимальное возможное число вершин дерева) и переменную
root:0..n. Каждая вершина хранимого T -дерева будет иметь номер
-- число от 1 до n. Разные вершины будут иметь разные номера.
Пометка в вершине с номером x равна
val[x]. Корень имеет номер
root. Если вершина с номером i имеет сыновей, то их
номера равны left[i] и right[i]. Отсутствующим сыновьям
соответствует число 0. Аналогичным образом значение root=0
соответствует пустому дереву.
Для хранения дерева используется лишь часть массива; для
тех i, которые свободны (не являются номерами вершин),
значения val[i] безразличны. Нам будет удобно, чтобы все
свободные числа были << связаны в список>>: первое хранится в
специальное переменной free:0..n, а следующее за i
свободное
число хранится в left[i], так что свободны числа
Вместо значения 0 (обозначающего отсутствие вершины) можно было бы воспользоваться любым другим числом вне 1..n. Чтобы подчеркнуть это, будем вместо 0 использовать константу null=0.
12.1.2. Составить программу, определяющую, содержится ли
элемент t:T в упорядоченном дереве (хранимом так, как только
что описано).
if root = null then begin
| ..не принадлежит
end else begin
| x := root;
| {инвариант: остается проверить наличие t в непустом
| поддереве с корнем 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 {left [x] <> null}
| | | x := left [x];
| | end else begin {t > val [x], right [x] <> null}
| | | x := right [x];
| | end;
| end;
| {либо t = val [x], либо t отсутствует в дереве}
| ..ответ = (t = val [x])
end;`
12.1.3. Упростить решение, используя следующий трюк.
Расширим область определения массива val, добавив ячейку с
номером null и положим val[null]=t.
val [null] := t; x := root; while t <> val [x] do begin | if t < val [x] then begin | | x := left [x]; | end else begin | | x := right [x]; | end; end; ..ответ: (x <> null).`
12.1.4. Составить программу добавления элемента t в
множество, представленное упорядоченным деревом (если элемент t
уже есть, ничего делать не надо).
procedure get_free (var i: integer);
begin
| {free <> null}
| i := free;
| free := left [free];
end;
С ее использованием программа приобретает вид:
if root = null then begin
| get_free (root);
| left [root] := null; right [root] := null;
| val [root] := t;
end else begin
| x := root;
| {инвариант: осталось добавить t к непустому поддереву с
| корнем в 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 := left [x];
| | end else begin {t > val [x]}
| | | x := right [x];
| | end;
| end;
| if t <> val [x] then begin {t нет в дереве}
| | get_free (i);
| | left [i] := null; right [i] := null;
| | val [i] := t;
| | if t < val [x] then begin
| | | left [x] := i;
| | end else begin {t > val [x]}
| | | right [x] := i;
| | end;
| end;
end;`
12.1.5. Составить программу удаления элемента t из
множества, представленного упорядоченным деревом (если его там
нет, ничего делать не надо).
if root = null then begin
| {дерево пусто, ничего делать не надо}
end else begin
| x := root;
| {осталось удалить t из поддерева с корнем в x; поскольку
| это может потребовать изменений в отце x, введем
| переменные father: 1..n и direction: (l, r);
| поддерживаем такой инвариант: если x не корень, то father
| - его отец, а direction равно l или r в зависимости от
| того, левым или правым сыном является 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
| | | father := x; direction := l;
| | | x := left [x];
| | end else begin {t > val [x]}
| | | father := x; direction := r;
| | | x := right [x];
| | end;
| end;
| {t = val [x] или t нет в дереве}
| if t = val [x] then begin
| | ..удаление вершины x с отцом father и
| | направлением direction
| end;
end;
Удаление вершины
использует процедуру
procedure make_free (i: integer); begin | left [i] := free; | free := i; end;Она включает число i в список свободных. При удалении различаются 4 случая в зависимости от наличия или отсутствия сыновей у удаляемой вершины.
if (left [x] = null) and (right [x] = null) then begin
| {x - лист, т.е. не имеет сыновей}
| make_free (x);
| if x = root then begin
| | root := null;
| end else if direction = l then begin
| | left [father] := null;
| end else begin {direction = r}
| | right [father] := null;
| end;
end else if (left[x]=null) and (right[x] <> null) then begin
| {x удаляется, а right [x] занимает место x}
| make_free (x);
| if x = root then begin
| | root := right [x];
| end else if direction = l then begin
| | left [father] := right [x];
| end else begin {direction = r}
| | right [father] := right [x];
| end;
end else if (left[x] <> null) and (right[x]=null) then begin
| ..симметрично
end else begin {left [x] <> null, right [x] <> null}
| ..удалить вершину с двумя сыновьями
end;
Удаление вершины с двумя сыновьями нельзя сделать просто так, но
ее можно предварительно поменять с вершиной, пометка на которой
является непосредственно следующим (в порядке возрастания)
элементом за пометкой на x.
y := right [x];
father := x; direction := r;
{теперь father и direction относятся к вершине y}
while left [y] <> null do begin
| father := y; direction := l;
| y := left [y];
end;
{val [y] - минимальная из пометок, больших val [x],
y не имеет левого сына}
val [x] := val [y];
..удалить вершину y (как удалять вершину, у которой нет
левого сына, мы уже знаем)`
12.1.6. Упростить программу удаления, заметив, что некоторые
случаи (например, первые два из четырех) можно объединить.![]()
12.1.7. Использовать упорядоченные деревья для представления
функций, область определения которых -- конечные множества
значений типа T, а значения имеют некоторый тип U. Операции:
вычисление значения на данном аргументе, изменение значения на
данном аргументе, доопределение функции на данном аргументе,
исключение элемента из области определения функции.
12.1.8. Предположим, что необходимо уметь также отыскивать
k-ый элемент множества (в порядке возрастания), причем
количество действий должно быть не более
. Какую дополнительную информацию надо хранить в
вершинах дерева?