Нарисуем точку. Из нее проведем две стрелки (влево вверх ивправо
вверх) в две другие точки. Из каждой из этих точек проведем
по две стрелки итак далее. Полученную картинку
(в $$n$$ -ом слое будет $$2^{n - 1}$$ точек) называют
Пусть выбрано некоторое конечное
пустое дерево, можно написать$$Tree(T) =\{empty\}+T\times Tree(T)\times Tree
(T).$$
Фиксируем некоторое $$T$$ -дерево. Для каждой его
вершины $$x$$ определено ее
Левое и правое
Пусть на множестве значений типа $$T$$ фиксирован порядок.
Назовем $$T$$ -дерево
14.1.1. Доказать, что в упорядоченном дереве все пометки различны.
Указание.
Каждое дерево будем считать представлением множества всех пометок на его вершинах. При этом одно и то же множество может иметь различные представления.
Благодаря упорядоченности каждый элемент может легко "найти свое место" в дереве: придя в какую-то вершину и сравнив себя с тем, кто там находится, элемент решает, идти ему налево или направо.$$\setlength{\unitlength}{1.2em} \begin{picture}(8,7) \put(4,2){\vector(0,1){1.5}} \put(3,1){\makebox(2,1){y}} \put(4,4){\makebox(0,0){x}} \put(3.5,4.5){\vector(-1,1){1.5}} \put(4.5,4.5){\vector(1,1){1.5}} \put(1,6){\makebox(2,1){y<x}} \put(5,6){\makebox(2,1){y>x}} \end{picture}$$ Начав с корня и двигаясь по этому правилу, он либо обнаружит, что такой элемент уже есть, либо найдет место, в котором он должен быть.
Всюду далее мы предполагаем, что на значениях типа $$T$$ задан порядок, и рассматриваем только упорядоченные деревья.
Можно было бы сопоставить вершины . Однако этот способ неэкономен, поскольку
тратится место на хранение пустых вакансий в полном двоичном
дереве.
Более экономен такой способ. Введем три массива
val: array [1..n] of T; left, right: array [1..n] of 0..n;
( n - максимальное возможное число вершин дерева)
И переменную . Каждая вершина хранимого $$T$$ -дерева будет иметь номер - число от 1 до n.
Разные вершины будут иметь разные номера. Пометка в вершине
с номером x равна . Корень имеет номер . Если вершина с номером i имеет сыновей, то их
номера равны и right[i]. Отсутствующим
сыновьям соответствует число 0. Аналогичным образом
значение соответствует пустому дереву.
Для хранения дерева используется лишь часть массива;
для тех i, которые свободны (не являются номерами
вершин), значения безразличны. Нам будет удобно,
чтобы все свободные числа были "связаны в список":
первое хранится в специальной переменной ,
а следующее за i свободное число хранится
в , так что свободны числа$${free, left[free], left[left[free]],...}$$
Для последнего свободного числа i значение
равно 0. означает, что свободных чисел больше
нет.
Замечание.Мы использовали для , но, конечно, с тем же успехом можно было
использовать массив right.
Вместо значения 0 (обозначающего отсутствие вершины) можно
было бы воспользоваться любым другим числом вне 1..n. Чтобы
подчеркнуть это, будем вместо 0 использовать .
14.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;
14.1.3.
Упростить решение, используя следующий трюк.
Расширим , добавив и положим .
Решение.
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).
14.1.4.
Составить программу добавления элемента t в множество,
представленное упорядоченным деревом (если элемент t
уже есть, ничего делать не надо).
Решение. Определим процедуру get_free (, дающую свободное (не являющееся номером)
число i и соответствующим образом корректирующую список
свободных чисел.
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;
14.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 (как удалять вершину, у которой нет
левого сына, мы уже знаем)
14.1.6. Упростить программу удаления, заметив, что некоторые случаи (например, первые два из четырех) можно объединить.
14.1.7.
Использовать упорядоченные деревья для представления
функций, T, а значения имеют некоторый тип U. Операции:
Решение. Делаем как раньше, добавив еще один массив
func\_val: array [1..n] of U;
если , func\_val[x] = u, то значение хранимой
функции на t равно u.
14.1.8.
Предположим, что необходимо уметь также отыскивать k -ый
Решение. В каждой вершине будем хранить число всех ее
потомков. Добавление и исключение вершины требует k -ой
вершины поддерживается такой s -ой вершиной x (здесь s и x - переменные).
Для каждой из операций (проверки, добавления и исключения)
количество действий не превосходит $$C\cdot(\text{высота
дерева})$$. Для "ровно подстриженного" дерева (когда все
листья на одной
Дерево называется
14.2.1.
Найти минимальное и максимальное возможное
количество вершин в
Решение. Максимальное число вершин равно $$2^n-1$$. Если $$m_n$$ - минимальное число вершин, то, как легко видеть, $$m_{n + 2} = 1 + m_n + m_{n+1}$$, откуда $$m_n =\Phi_{n+2} - 1$$ ( $$\Phi_n$$ - $$n$$ -ое число Фибоначчи, $$\Phi_1=1$$, $$\Phi_2=1$$, $$\Phi_{n+2} = \Phi_n +\Phi_{n+1}$$ ).
14.2.2.
Доказать, что
Решение.
Мы хотим восстанавливать
Пусть вершина $$a$$ имеет правого сына $$b$$. Обозначим
через $$P$$
Пусть $$b$$ - правый сын $$a$$, $$c$$ - левый
сын $$b$$, $$P$$ -
Такой же порядок соответствует дереву с корнем $$c$$, имеющим
левого сына $$a$$ и правого сына $$b$$, для которого $$P$$ и $$Q$$ -
14.2.3.
Дано дерево, сбалансированное всюду, кроме корня, в котором
разница высот равна $$2$$ (т.е. левое и правое
Решение. Пусть более низким является, например, левое
14.2.4.
В
Решение. Будем доказывать более общий факт:
Лемма. Если в
Частным случаем прививки является замена пустого
По предыдущей задаче вращение преобразует
14.2.5.
Составить программы добавления и
Решение. Будем хранить для каждой вершины разницу между
(2) После преобразований мы должны также изменить
соответственно значения в массиве diff. Для этого достаточно знать
Вот процедуры вращений:
procedure SR (a:integer); {малое правое вращение с корнем a}
| var b: 1..n; val_a,val_b: T; h_P,h_Q,h_R: integer;
begin
| b := right [a]; {b <> null}
| val_a := val [a]; val_b := val [b];
| h_Q := 0; h_R := diff[b]; h_P := (max(h_Q,h_R)+1)-diff[a];
| val [a] := val_b; val [b] := val_a;
| right [a] := right [b] {поддерево R}
| right [b] := left [b] {поддерево Q}
| left [b] := left [a] {поддерево P}
| left [a] := b;
| diff [b] := h_Q - h_P;
| diff [a] := h_R - (max (h_P, h_Q) + 1);
end;
procedure BR(a:integer);{большое правое вращение с корнем a}
| var b,c: 1..n; val_a,val_b,val_c: T;
| h_P,h_Q,h_R,h_S: integer;
begin
| b := right [a]; c := left [b]; {,c <> null}
| val_a := val [a]; val_b := val [b]; val_c := val [c];
| h_Q := 0; h_R := diff[c]; h_S := (max(h_Q,h_R)+1)+diff[b];
| h_P := 1 + max (h_S, h_S-diff[b]) - diff [a];
| val [a] := val_c; val [c] := val_a;
| left [b] := right [c] {поддерево R}
| right [c] := left [c] {поддерево Q}
| left [c] := left [a] {поддерево P}
| left [a] := c;
| diff [b] := h_S - h_R;
| diff [c] := h_Q - h_P;
| diff [a] := max (h_S, h_R) - max (h_P, h_Q);
end;
Левые вращения (большое и малое) записываются
Процедуры добавления и diff и восстановлением сбалансированности.
При этом используется процедура с такими свойствами:
дано: левое и правое a
сбалансированы, в самой вершине разница высот не больше 2,
в diff заполнен правильно;
надо: diff
соответственно изменен, d - изменение его diff
procedure balance (a: integer; var d: integer);
begin {-2 <= diff[a] <= 2}
| if diff [a] = 2 then begin
| | b := right [a];
| | if diff [b] = -1 then begin
| | | BR (a); d := -1;
| | end else if diff [b] = 0 then begin
| | | SR (a); d := 0;
| | end else begin {diff [b] = 1}
| | | SR (a); d := - 1;
| | end;
| end else if diff [a] = -2 then begin
| | b := left [a];
| | if diff [b] = 1 then begin
| | | BL (a); d := -1;
| | end else if diff [b] = 0 then begin
| | | SL (a); d := 0;
| | end else begin {diff [b] = -1}
| | | SL (a); d := - 1;
| | end;
| end else begin {-2 < diff [a] < 2, ничего делать не надо}
| | d := 0;
| end;
end;
Восстановление сбалансированности требует движения от
листьев к корню, поэтому будем хранить в стеке путь от корня к рассматриваемой в данный момент вершине. Элементами
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 и (их роль теперь
играет
14.2.6. Доказать, что при добавлении элемента
(а) второй из трех случаев балансировки (см.рисунок к задаче 14.2.3.) невозможен;
(б) полная балансировка требует не более одного вращения (после чего все дерево становится сбалансированным), в то время как при удалении элемента может понадобиться много вращений.
Замечание. Мы старались записать программы добавления и удаления так, чтобы они были как можно более похожими друг на друга. Используя специфику каждой из них, можно многое упростить.
Существуют и другие способы представления множеств, гарантирующие число действий порядка $$log n$$ на каждую операцию. Опишем один из них (называемый Б-деревьями ).
До сих пор каждая вершина содержала один элемент хранимого
множества. Этот элемент служил границей между левым
и правым поддеревом. Будем теперь хранить в вершине $$k\ge1$$
Добавление элемента происходит так. Если лист, в который он попадает, неполон (т.е. содержит
менее $$2t$$ элементов), то нет проблем. Если он полон, то $$2t+1$$ элемент (все элементы листа и новый элемент)
разбиваем на два листа по $$t$$ элементов и разделяющий их
серединный элемент. Этот серединный элемент надо добавить
в вершину предыдущего уровня. Это возможно, если в ней
менее $$2t$$ элементов. Если и она полна, то ее разбивают на
две, выделяют серединный элемент и т.д. Если в конце концов
мы захотим добавить элемент в корень, а он окажется полным,
то корень расщепляется на две вершины, а
Удаление элемента, находящегося не в листе, сводится к удалению непосредственно следующего за ним,
который находится в листе. Поэтому достаточно научиться удалять
элемент из листа. Если лист при этом становится слишком маленьким,
то его можно пополнить за счет соседнего листа - если только и он не
имеет минимально возможный размер $$t$$. Если же оба листа имеют размер $$t$$, то на них вместе $$2t$$ элементов, вместе с разделителем - $$2t+1$$. После удаления одного элемента остается $$2t$$ элементов - как раз на один лист. Если при этом вершина предыдущего уровня становится меньше
14.2.7.
Реализовать описанную схему хранения множеств,
убедившись, что она также позволяет обойтись $$C \log n$$ действий для
14.2.8.
Можно определять
Указание. Он также использует большие и малые вращения. Подробности см.в книге Рейнгольда, Нивергельта и Део "Комбинаторные алгоритмы".
Нарисуем точку. Из нее проведем две стрелки (влево вверх ивправо
вверх) в две другие точки. Из каждой из этих точек проведем
по две стрелки итак далее. Полученную картинку
(в $$n$$ -ом слое будет $$2^{n - 1}$$ точек) называют
Пусть выбрано некоторое конечное
пустое дерево, можно написать$$Tree(T) =\{empty\}+T\times Tree(T)\times Tree
(T).$$
Фиксируем некоторое $$T$$ -дерево. Для каждой его
вершины $$x$$ определено ее
Левое и правое
Пусть на множестве значений типа $$T$$ фиксирован порядок.
Назовем $$T$$ -дерево
14.1.1. Доказать, что в упорядоченном дереве все пометки различны.
Указание.
Каждое дерево будем считать представлением множества всех пометок на его вершинах. При этом одно и то же множество может иметь различные представления.
Благодаря упорядоченности каждый элемент может легко "найти свое место" в дереве: придя в какую-то вершину и сравнив себя с тем, кто там находится, элемент решает, идти ему налево или направо.$$\setlength{\unitlength}{1.2em} \begin{picture}(8,7) \put(4,2){\vector(0,1){1.5}} \put(3,1){\makebox(2,1){y}} \put(4,4){\makebox(0,0){x}} \put(3.5,4.5){\vector(-1,1){1.5}} \put(4.5,4.5){\vector(1,1){1.5}} \put(1,6){\makebox(2,1){y<x}} \put(5,6){\makebox(2,1){y>x}} \end{picture}$$ Начав с корня и двигаясь по этому правилу, он либо обнаружит, что такой элемент уже есть, либо найдет место, в котором он должен быть.
Всюду далее мы предполагаем, что на значениях типа $$T$$ задан порядок, и рассматриваем только упорядоченные деревья.
Можно было бы сопоставить вершины . Однако этот способ неэкономен, поскольку
тратится место на хранение пустых вакансий в полном двоичном
дереве.
Более экономен такой способ. Введем три массива
val: array [1..n] of T; left, right: array [1..n] of 0..n;
( n - максимальное возможное число вершин дерева)
И переменную . Каждая вершина хранимого $$T$$ -дерева будет иметь номер - число от 1 до n.
Разные вершины будут иметь разные номера. Пометка в вершине
с номером x равна . Корень имеет номер . Если вершина с номером i имеет сыновей, то их
номера равны и right[i]. Отсутствующим
сыновьям соответствует число 0. Аналогичным образом
значение соответствует пустому дереву.
Для хранения дерева используется лишь часть массива;
для тех i, которые свободны (не являются номерами
вершин), значения безразличны. Нам будет удобно,
чтобы все свободные числа были "связаны в список":
первое хранится в специальной переменной ,
а следующее за i свободное число хранится
в , так что свободны числа$${free, left[free], left[left[free]],...}$$
Для последнего свободного числа i значение
равно 0. означает, что свободных чисел больше
нет.
Замечание.Мы использовали для , но, конечно, с тем же успехом можно было
использовать массив right.
Вместо значения 0 (обозначающего отсутствие вершины) можно
было бы воспользоваться любым другим числом вне 1..n. Чтобы
подчеркнуть это, будем вместо 0 использовать .
14.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;
14.1.3.
Упростить решение, используя следующий трюк.
Расширим , добавив и положим .
Решение.
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).
14.1.4.
Составить программу добавления элемента t в множество,
представленное упорядоченным деревом (если элемент t
уже есть, ничего делать не надо).
Решение. Определим процедуру get_free (, дающую свободное (не являющееся номером)
число i и соответствующим образом корректирующую список
свободных чисел.
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;
14.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 (как удалять вершину, у которой нет
левого сына, мы уже знаем)
14.1.6. Упростить программу удаления, заметив, что некоторые случаи (например, первые два из четырех) можно объединить.
14.1.7.
Использовать упорядоченные деревья для представления
функций, T, а значения имеют некоторый тип U. Операции:
Решение. Делаем как раньше, добавив еще один массив
func\_val: array [1..n] of U;
если , func\_val[x] = u, то значение хранимой
функции на t равно u.
14.1.8.
Предположим, что необходимо уметь также отыскивать k -ый
Решение. В каждой вершине будем хранить число всех ее
потомков. Добавление и исключение вершины требует k -ой
вершины поддерживается такой s -ой вершиной x (здесь s и x - переменные).
Для каждой из операций (проверки, добавления и исключения)
количество действий не превосходит $$C\cdot(\text{высота
дерева})$$. Для "ровно подстриженного" дерева (когда все
листья на одной
Дерево называется
14.2.1.
Найти минимальное и максимальное возможное
количество вершин в
Решение. Максимальное число вершин равно $$2^n-1$$. Если $$m_n$$ - минимальное число вершин, то, как легко видеть, $$m_{n + 2} = 1 + m_n + m_{n+1}$$, откуда $$m_n =\Phi_{n+2} - 1$$ ( $$\Phi_n$$ - $$n$$ -ое число Фибоначчи, $$\Phi_1=1$$, $$\Phi_2=1$$, $$\Phi_{n+2} = \Phi_n +\Phi_{n+1}$$ ).
14.2.2.
Доказать, что
Решение.
Мы хотим восстанавливать
Пусть вершина $$a$$ имеет правого сына $$b$$. Обозначим
через $$P$$
Пусть $$b$$ - правый сын $$a$$, $$c$$ - левый
сын $$b$$, $$P$$ -
Такой же порядок соответствует дереву с корнем $$c$$, имеющим
левого сына $$a$$ и правого сына $$b$$, для которого $$P$$ и $$Q$$ -
14.2.3.
Дано дерево, сбалансированное всюду, кроме корня, в котором
разница высот равна $$2$$ (т.е. левое и правое
Решение. Пусть более низким является, например, левое
14.2.4.
В
Решение. Будем доказывать более общий факт:
Лемма. Если в
Частным случаем прививки является замена пустого
По предыдущей задаче вращение преобразует
14.2.5.
Составить программы добавления и
Решение. Будем хранить для каждой вершины разницу между
(2) После преобразований мы должны также изменить
соответственно значения в массиве diff. Для этого достаточно знать
Вот процедуры вращений:
procedure SR (a:integer); {малое правое вращение с корнем a}
| var b: 1..n; val_a,val_b: T; h_P,h_Q,h_R: integer;
begin
| b := right [a]; {b <> null}
| val_a := val [a]; val_b := val [b];
| h_Q := 0; h_R := diff[b]; h_P := (max(h_Q,h_R)+1)-diff[a];
| val [a] := val_b; val [b] := val_a;
| right [a] := right [b] {поддерево R}
| right [b] := left [b] {поддерево Q}
| left [b] := left [a] {поддерево P}
| left [a] := b;
| diff [b] := h_Q - h_P;
| diff [a] := h_R - (max (h_P, h_Q) + 1);
end;
procedure BR(a:integer);{большое правое вращение с корнем a}
| var b,c: 1..n; val_a,val_b,val_c: T;
| h_P,h_Q,h_R,h_S: integer;
begin
| b := right [a]; c := left [b]; {,c <> null}
| val_a := val [a]; val_b := val [b]; val_c := val [c];
| h_Q := 0; h_R := diff[c]; h_S := (max(h_Q,h_R)+1)+diff[b];
| h_P := 1 + max (h_S, h_S-diff[b]) - diff [a];
| val [a] := val_c; val [c] := val_a;
| left [b] := right [c] {поддерево R}
| right [c] := left [c] {поддерево Q}
| left [c] := left [a] {поддерево P}
| left [a] := c;
| diff [b] := h_S - h_R;
| diff [c] := h_Q - h_P;
| diff [a] := max (h_S, h_R) - max (h_P, h_Q);
end;
Левые вращения (большое и малое) записываются
Процедуры добавления и diff и восстановлением сбалансированности.
При этом используется процедура с такими свойствами:
дано: левое и правое a
сбалансированы, в самой вершине разница высот не больше 2,
в diff заполнен правильно;
надо: diff
соответственно изменен, d - изменение его diff
procedure balance (a: integer; var d: integer);
begin {-2 <= diff[a] <= 2}
| if diff [a] = 2 then begin
| | b := right [a];
| | if diff [b] = -1 then begin
| | | BR (a); d := -1;
| | end else if diff [b] = 0 then begin
| | | SR (a); d := 0;
| | end else begin {diff [b] = 1}
| | | SR (a); d := - 1;
| | end;
| end else if diff [a] = -2 then begin
| | b := left [a];
| | if diff [b] = 1 then begin
| | | BL (a); d := -1;
| | end else if diff [b] = 0 then begin
| | | SL (a); d := 0;
| | end else begin {diff [b] = -1}
| | | SL (a); d := - 1;
| | end;
| end else begin {-2 < diff [a] < 2, ничего делать не надо}
| | d := 0;
| end;
end;
Восстановление сбалансированности требует движения от
листьев к корню, поэтому будем хранить в стеке путь от корня к рассматриваемой в данный момент вершине. Элементами
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 и (их роль теперь
играет
14.2.6. Доказать, что при добавлении элемента
(а) второй из трех случаев балансировки (см.рисунок к задаче 14.2.3.) невозможен;
(б) полная балансировка требует не более одного вращения (после чего все дерево становится сбалансированным), в то время как при удалении элемента может понадобиться много вращений.
Замечание. Мы старались записать программы добавления и удаления так, чтобы они были как можно более похожими друг на друга. Используя специфику каждой из них, можно многое упростить.
Существуют и другие способы представления множеств, гарантирующие число действий порядка $$log n$$ на каждую операцию. Опишем один из них (называемый Б-деревьями ).
До сих пор каждая вершина содержала один элемент хранимого
множества. Этот элемент служил границей между левым
и правым поддеревом. Будем теперь хранить в вершине $$k\ge1$$
Добавление элемента происходит так. Если лист, в который он попадает, неполон (т.е. содержит
менее $$2t$$ элементов), то нет проблем. Если он полон, то $$2t+1$$ элемент (все элементы листа и новый элемент)
разбиваем на два листа по $$t$$ элементов и разделяющий их
серединный элемент. Этот серединный элемент надо добавить
в вершину предыдущего уровня. Это возможно, если в ней
менее $$2t$$ элементов. Если и она полна, то ее разбивают на
две, выделяют серединный элемент и т.д. Если в конце концов
мы захотим добавить элемент в корень, а он окажется полным,
то корень расщепляется на две вершины, а
Удаление элемента, находящегося не в листе, сводится к удалению непосредственно следующего за ним,
который находится в листе. Поэтому достаточно научиться удалять
элемент из листа. Если лист при этом становится слишком маленьким,
то его можно пополнить за счет соседнего листа - если только и он не
имеет минимально возможный размер $$t$$. Если же оба листа имеют размер $$t$$, то на них вместе $$2t$$ элементов, вместе с разделителем - $$2t+1$$. После удаления одного элемента остается $$2t$$ элементов - как раз на один лист. Если при этом вершина предыдущего уровня становится меньше
14.2.7.
Реализовать описанную схему хранения множеств,
убедившись, что она также позволяет обойтись $$C \log n$$ действий для
14.2.8.
Можно определять
Указание. Он также использует большие и малые вращения. Подробности см.в книге Рейнгольда, Нивергельта и Део "Комбинаторные алгоритмы".
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.