Задача поиска данных в большом массиве (или файле) возникает очень часто (во всех
приложениях, работающих с базами данных, но не только в них). Если массив упорядочен,
можно использовать метод деления отрезка пополам. Для этого нужно приблизительно Log(N)
сравнений - совсем немного. Но если тебуется вставлять новые данные и удалять имеющиеся,
затраты на поддержание порядка могут быть непомерны - в худшем случае для вставки и для
удаления потребуется N операций. Если не поддерживать порядок данных - непомерны будут
затраты на поиск. Кроме того, для разных целей может быть нужен разный порядок данных,
а физический порядок всего один. Решение состоит в поддержке индексов - дополнительных
упорядоченных структур данных, содержащих ключи и ссылки на основные данные. Порядок
самих данных при этом не играет никакой роли. Индексный поиск может быть столь же
эффективен, как и поиск методом деления деления в упорядоченном массиве, плюс к этому
вставки и удаления также могут выполняться быстро. Существует несколько способов
построения индексов. Во-первых, это так называемые B-деревья. В определенном смысле
B-дерево - обобщение двоичного дерева - простой конструкции, не обладающей, однако,
гарантированной эффективностью. Но B-дерево - не единственнная эффективная конструкция
индекса. Можно упомянуть красно-черные деревья - еще одно усовершенствование двоичных
деревьев (в настоящее время у меня нет соответствующего примера).
Упоминание о красно-черных деревьях по-видимому было следствием покупки книги
Т.Кормена, Ч.Лейзерсона и Р.Ривеста "Алгоритмы: построение и анализ", но разобраться
в их устройстве быстро не удалость и дело это было заброшено. В-деревья казались
значительно более важными (наверное, это так и есть) и понимание их устрйства
я посчитал для себя достаточным. Значительно позже я вернулся к этому делу
и написал еще один пример, демонстрирущий АВЛ-дерево.
А описание красно-черных деревьях в упомянутой книге представляется методически неверным.
По какой-то непонятной причине не сказано, что красно-черное дерево есть 2-3-4-дерево
(т.е. B-дерево, в страницах которого могут находиться до трех ключей и четырех ссылок
на нижележащие страницы), представленное в виде двоичного дерева. И рассматриваются
красно-черные деревья до B-деревьев. Ясно это стало после ознакомления с учебным курсом
Алгоритмы на C++
Р.Седжвика. Красный цвет указывет на то, что узел является дополнительным и принадлежит
тому же уровню, что и основной узел черного цвета. Можно построить аналогичную несимметрчную конструкцию,
в которой дополнительные узлы могут быть только с одной стороны от черных (например,
слева) - это эквивалент 2-3-дерева. Возможно, она не хуже.
Из соображений простоты начать лучше именно с двоичных деревьев. Всюду ниже (также для
простоты) предполагается, что индексируемые данные находятся в оперативной памяти и в
индексе содержатся только ссылки на эти данные.
Двоичное дерево - структура данных, состоящая из узлов, каждый из которых
содержит ключ и две ссылки на поддеревья - левое и правое. Все ключи в левом
поддереве меньше ключа в узле, все ключи в правом поддереве - больше. Требование
точного неравенства ключей естественно, т.к. даже ести реальные ключи могут повторяться,
уникальность достигается расширением ключа номером (смещением, адресом) элемента данных.
Процедуры вставки и удаления ключей достаточно просты:
define nNODE 8192
struct TNode
int pData;
int pLeft;
int pRight;
section
int pFree;
end
TNode Node[nNODE];
int pRoot;
int pFree;
void Stop(char @S);
void Init()
pRoot=-1;
pFree=-1;
int P= 0;
while PP2:
return 1;
end
return 0;
end
void Insert(int pData)
int @P=@pRoot;
while P>=0 do
int C=Comp(Node[P].pData,pData);
select
case C>0:
@P=@Node[P].pLeft;
case C<0:
@P=@Node[P].pRight;
default:
return // Повтор ключа
end
end
if pFree<0 then
Stop("Недостаточно свободных узлов");
end
P =pFree;
pFree = Node[pFree].pFree;
Node[P].pData =pData;
Node[P].pLeft =-1;
Node[P].pRight=-1;
end
void Delete(int pData)
int @P=@pRoot;
while TRUE do
if P<0 then
return // Ключ не найден
end
int C=Comp(Node[P].pData,pData);
select
case C>0:
@P=@Node[P].pLeft;
case C<0:
@P=@Node[P].pRight;
default:
exit
end
end
int P2=P;
if Node[P2].pLeft<0 then
P=Node[P2].pRight;
Node [P2].pFree=pFree;
pFree= P2;
return
end
if Node[P2].pRight<0 then
P=Node[P2].pLeft;
Node [P2].pFree=pFree;
pFree=P2;
return
end
int @P1=@Node[P2].pLeft;
while Node[P1].pRight>=0 do
@P1=@Node[P1].pRight;
end
Node[P].pData=Node[P1].pData;
int P0=P1;
P1=Node[P1].pLeft;
Node [P0].pFree=pFree;
pFree = P0;
end
Изменение ключа реализуется посредством удаления старого и вставки нового.
Двоичное дерево имеет существенный недостаток - в худшем случае (например,
при вставке упорядоченнной последовательности ключей) оно вырождается в
список. Этот недостаток может быть устранен, ести предусмотреть в узле место
для двух ключей и трех ссылок (в общем случае N ключей и (N+1) ссылки) и усложнить
функции вставки/удаления. Новый ключ всегда добавляется в нижнюю страницу, если
страница переполняется, она делится на две и средний ключ переносится на вышележащую
страницу. Если и эта страница переполняется, она тоже делится на две. В худшем
случае корневая страница будет поделена на две и будет создан новый корень,
содержащий единственный ключ. Высота дерева при этом увеличится на единицу. При
удалении наоборот, если страница становится заполненной менее чем наполовину, она
либо дополняется ключом из соседней страницы, либо две страницы объединяются (при
этом один ключ берется из вышележащей страницы).
Пример был написан когда еще использовалась MS-DOS, для экономии памяти ссылки
на страницы были отделены от самих страниц (все нижние страницы, а их больше всего,
содержат лишь пустые ссылки). По какой-то неизвестной причине процедуры нерекурсивны,
ниже приведена рекурсивная реализация:
define nDATA 8 // >=2
define nDATA1 9 // nDATA+1
define nPAGE 2048
define nLINK 512
define nPATH 32
struct TPage
int pData[nDATA];
int pLink;
int nData;
section
int pPage;
end
struct TLink
int pPage[nDATA1];
section
int pLink;
end
struct TPath
int pPage;
int pData;
end
TPage Page[nPAGE];
TLink Link[nLINK];
int pPage; // Указатель на список свободных стpаниц данных
int pLink; // Указатель на список свободных стpаниц ссылок
int pRoot; // Указатель на коpневую стpаницу деpева
void Stop(char @S);
void Init()
pPage=-1;
int P= 0;
while PP2:
return 1;
end
return 0;
end
void Insert(int pData)
TPath Path[nPATH];
int N;
int P;
P=pRoot;
N=0;
while P>=0 do
if N>=nPATH then
Stop("Высота деpева слишком велика");
end
int I=0;
int J=Page[P].nData;
while I0:
J=K;
case S<0:
I=K+1;
default:
return // Повтоp ключа
end
end
Path[N].pPage=P;
Path[N].pData=I;
inc N;
int L=Page[P].pLink;
if L<0 then
exit
end
P=Link[L].pPage[I];
end
int P0=-1;
int P1=-1;
while TRUE do
dec N;
if N<0 then
if pPage<0 then
Stop("Недостаточно стpаниц данных");
end
pRoot=pPage;
pPage=Page[pPage].pPage;
Page[pRoot].pData[0]= pData;
Page[pRoot].pLink =-1;
Page[pRoot].nData = 1;
if P0<0 then
exit
end
if pLink<0 then
Stop("Недостаточно стpаниц ссылок");
end
int L=pLink;
pLink=Link[pLink].pLink;
Link[L].pPage[0]=P0;
Link[L].pPage[1]=P1;
Page[pRoot].pLink=L;
exit
end
P=Path[N].pPage;
if Page[P].nDataPath[N].pData do
Page[P].pData[I]=Page[P].pData[I-1];
dec I;
end
Page[P].pData[I]=pData;
inc Page[P].nData;
if P0<0 then
exit
end
int L=Page[P].pLink;
int J=Page[P].nData; // Новое значение
while J>Path[N].pData do
Link[L].pPage[J]=Link[L].pPage[J-1];
dec J;
end
Link[L].pPage[J] =P0;
Link[L].pPage[J+1]=P1;
exit
end
if pPage<0 then
Stop("Недостаточно стpаниц данных");
end
int P2=pPage;
pPage =Page[pPage].pPage;
int L, L2=-1;
if P0>=0 then
if pLink<0 then
Stop("Недостаточно стpаниц ссылок");
end
L =Page[P].pLink;
L2 =pLink;
pLink=Link[pLink].pLink;
end
Page[P2].pLink=L2;
int M=nDATA/2;
if Path[N].pData<=M then
int I=M;
int J=0;
while IPath[N].pData do
Page[P].pData[I]=Page[P].pData[I-1];
dec I;
end
Page[P].pData[I]=pData;
if L2>=0 then
I=M+1;
J=1;
while I<=nDATA do
Link[L2].pPage[J]=Link[L].pPage[I];
inc J;
inc I;
end
I=M+1;
while I>Path[N].pData do
Link[L].pPage[I]=Link[L].pPage[I-1];
dec I;
end
Link[L].pPage[I] =P0;
Link[L].pPage[I+1]=P1;
Link[L2].pPage[0]=Link[L].pPage[M+1];
end
else
int I=M+1;
int J=0;
while I=0 then
I=M+1;
J=0;
while I=nPATH then
Stop("Высота деpева слишком велика");
end
int S=1;
int I=0;
int J=Page[P].nData;
while I0:
J=K;
case S<0:
I=K+1;
default:
I=K;
exit
end
end
Path[N].pPage=P;
Path[N].pData=I;
inc N;
if S=0 then
if Page[P].pLink>=0 then
int P1=Link[Page[P].pLink].pPage[I];
while TRUE do
if N>=nPATH then
Stop("Высота деpева слишком велика");
end
Path[N].pPage=P1;
Path[N].pData=Page[P1].nData;
inc N;
if Page[P1].pLink<0 then
exit
end
P1=Link[Page[P1].pLink].pPage[Page[P1].nData];
end
dec Page[P1].nData;
Page[P].pData[I]=Page[P1].pData[Page[P1].nData];
else
while I+11 do
dec N;
if Page[Path[N].pPage].nData>=nDATA/2 then
return
end
int P0;
int P1;
int M;
int S;
P=Path[N-1].pPage;
M=Path[N-1].pData;
S=M;
if MnDATA then
if S=M then // Стpаница P0 недостаточно заполнена
Page[P0].pData[Page[P0].nData]=Page[P] .pData[M];
Page[P] .pData[M] =Page[P1].pData[0];
int I=0;
while I+1=0 then
Link[L0].pPage[Page[P0].nData+1]=Link[L1].pPage[0];
I=0;
while I+1<=Page[P1].nData do
Link[L1].pPage[I]=Link[L1].pPage[I+1];
inc I;
end
end
inc Page[P0].nData;
dec Page[P1].nData;
else // Стpаница P1 недостаточно заполнена
int I=Page[P1].nData;
while I>0 do
Page[P1].pData[I]=Page[P1].pData[I-1];
dec I;
end
Page[P1].pData[0]=Page[P] .pData[M];
Page[P] .pData[M]=Page[P0].pData[Page[P0].nData-1];
if L0>=0 then
I=Page[P1].nData+1;
while I>0 do
Link[L1].pPage[I]=Link[L1].pPage[I-1];
dec I;
end
Link[L1].pPage[0]=Link[L0].pPage[Page[P0].nData];
end
dec Page[P0].nData;
inc Page[P1].nData;
end
return
end
// слияние стpаниц
int I=Page[P0].nData;
int K=I;
Page[P0].pData[I]=Page[P].pData[M];
inc I;
int J=0;
while J=0 then
I=K+1;
J=0;
while J<=Page[P1].nData do
Link[L0].pPage[I]=Link[L1].pPage[J];
inc I;
inc J;
end
Link[L1].pLink=pLink;
pLink =L1;
end
Page[P1].pPage=pPage;
pPage =P1;
I=M;
while I+10 then
return
end
int L=Page[pRoot].pLink;
if L>=0 then
Page[pRoot].pPage=pPage; pPage=pRoot;
pRoot=Link[L].pPage[0];
Link[L].pLink=pLink;
pLink =L;
return
end
Page[pRoot].pPage=pPage;
pPage =pRoot;
pRoot =-1;
end
Эти процедуры гарантируют, что все страницы дерева (кроме корневой) будут
заполнены не менее чем на половину. В силу этого высота дерева (и затраты
времени на вставку, удаление и поиск) никогда не будут большими. Сам поиск
значительно проще вставки и удаления. Поиск единственного ключа очень прост,
с него начинается функция Delete и повторять этот фрагмент еще раз нет смысла,
поиск ключей из заданного диапазона немного сложнее, но все равно он значительно
проще вставки/удаления:
void search(int P; int Max)
int I=0;
while I=0 then
search(Link[Page[P].pLink].pPage[I],Max);
end
if Comp(Page[P].pData[I],Max)>0 then
return
end
puts(@Str(Page[P].pData[I]));
putc(#13);
putc(#10);
inc I;
end
if Page[P].pLink>=0 then
search(Link[Page[P].pLink].pPage[I],Max);
end
end
void Select(int P; int Min, Max)
if P<0 then
return
end
int I=0;
int J=Page[P].nData;
while I=0 then
J=K;
else
I=K+1;
end
end
if Page[P].pLink>=0 then
Select(Link[Page[P].pLink].pPage[I],Min,Max);
end
while I0 then
return
end
puts(@Str(Page[P].pData[I]));
putc(#13);
putc(#10);
if Page[P].pLink>=0 then
search(Link[Page[P].pLink].pPage[I+1],Max);
end
inc I;
end
end
Функция Select печатает все ключи, находящиеся в диапазоне от Min до Max.
B-дерево легко адаптируеся для случая, когда данные размещаются на дисках.
Для этого нужно хранить ключи, ссылки на данные и ссылки на страницы индекса
вместе (в приведенном примере ключи доступны по ссылкам на данные, а ссылки на
страницы хранятся отдельно от ссылок на данные для экономии памяти - большинство
страниц не содержат ссылок на другие страницы). Размер страницы должен выбираться
не слишком малым.
Эффективность B-деревьев возрастает с увеличением размера массива данных.
Из-за этого пример B-дерева в памяти размером 64K не очень показателен.
Например, если использовать его для поиска в таблице меток ассемблера asm8086,
выигрыш по ставнению с простым перебором при компиляции context.asm (около двух
тысяч меток) будет примерно пятикратным. Это несколько хуже, чем поиск методом
половинного деления, но зато B-дерево [почти] не зависит от порядка меток.
Рекурсивная реализация (компилятор - Context3, ревизия не ниже 82):
define NO_MEMORY -3;
define DUP -1;
define NOT_FOUND -2;
define OK 0;
define MOVE_UP 1;
define BALANCE 1;
define nDATA 2;
struct PAGE
int nData;
int data [nDATA];
PAGE link [nDATA + 1]^;
end
define nPAGE 0x100000;
PAGE pages[nPAGE];
PAGE free^ := NULL;
void initPages() is
int i := nPAGE;
while i > 0 do
dec i;
pages[i].link[0]^ := free^;
free^ := pages[i]^;
end
end
int comp(int p1, int p2) is
select
case p1 < p2:
return -1;
case p1 > p2:
return 1;
end
return 0;
end
PAGE createPage()^ is
PAGE temp^ := free^;
if temp^ != NULL then
free^ := free.link[0]^;
int i := 0;
while i < nDATA do
temp.data[i] := 0;
temp.link[i]^ := NULL;
inc i;
end
temp.link[i]^ := NULL;
return temp^;
end
return NULL;
end
void deletePage(PAGE page^) is
page.link[0]^ := free^;
free^ := page^;
end
PAGE root^ := NULL;
int split(PAGE page^^, int i) is
int result := MOVE_UP;
PAGE p1^ := page.link[i]^;
if page.nData < nDATA then
int k := page.nData;
while k > i do
page.data[k] := page.data[k - 1];
dec k;
end
int m := page.nData + 1;
while m > i + 1 do
page.link[m]^ := page.link[m - 1]^;
dec m;
end
inc page.nData;
page.data [i] := p1 .data[0];
page.link [i]^ := p1 .link[0]^;
page.link [i + 1]^ := p1 .link[1]^;
deletePage(p1^);
result := OK;
else
PAGE p2^ := createPage()^;
if p2^ = NULL then
return NO_MEMORY;
end
select
case i < nDATA / 2:
int k := nDATA / 2;
int m := 0;
while k < nDATA do
p2.data[m] := page.data[k];
p2.link[m]^ := page.link[k]^;
inc m;
inc k;
end
p2 .link[m]^ := page.link[k]^;
p2 .nData := m;
int n := nDATA / 2;
while n > i do
page.data[n] := page.data[n - 1];
page.link[n]^ := page.link[n - 1]^;
dec n;
end
page.data[i] := p1.data[0];
page.link[i]^ := p1.link[0]^;
page.link[i + 1]^ := p1.link[1]^;
page.nData := nDATA / 2;
p1.data[0] := page.data[nDATA / 2];
case i = nDATA / 2:
int k := nDATA / 2;
int m := 0;
while k < nDATA do
p2.data[m] := page.data[k];
p2.link[m]^ := page.link[k]^;
inc m;
inc k;
end
p2 .link[m]^ := page.link[k]^;
p2 .nData := m;
page.link[i]^ := p1.link[0]^;
//INFO Overwrite
p2 .link[0]^ := p1.link[1]^;
page.nData := nDATA / 2;
default:
int k := nDATA / 2 + 1;
int m := 0;
while k < i do
p2.data[m] := page.data[k];
p2.link[m]^ := page.link[k]^;
inc m;
inc k;
end
p2.data[m] := p1 .data[0];
p2.link[m]^ := p1 .link[0]^;
inc m;
int n := m;
while k < page.nData do
p2.data[m] := page.data[k];
p2.link[m]^ := page.link[k]^;
inc m;
inc k;
end
p2 .link[m]^ := page.link[k]^;
//INFO Overwrite
p2.link[n]^ := p1 .link[1]^;
p2.nData := m;
page.nData := nDATA / 2;
p1.data[0] := page.data[nDATA / 2];
end
p1.link[0]^ := page^;
p1.link[1]^ := p2^;
p1.nData := 1;
page^ := p1^;
end
return result;
end
int insert(PAGE page^^, int data) is
if page^ = NULL then
page^ := createPage()^;
if page^ = NULL then
return NO_MEMORY;
end
page.nData := 1;
page.data[0] := data;
page.link[0]^ := NULL;
page.link[1]^ := NULL;
return MOVE_UP;
else
int i := 0;
int j := page.nData;
while i < j do
int m := (i + j) /2;
int c := comp(data, page.data[m]);
select
case c = 0:
return DUP;
case c < 0:
j := m;
default:
i := m + 1;
end
end
int result := insert(page.link[i]^^, data);
if result > OK then
result := split(page^^, i);
end
return result;
end
end
int balance(PAGE page^, int i) is
select
case i = page.nData:
PAGE p1^ := page.link[i - 1]^;
PAGE p2^ := page.link[i]^;
if p1.nData + p2.nData + 1 <= nDATA then
int j := p1.nData;
p1.data[j]:= page.data[i - 1];
inc j;
int k := 0;
while k < p2.nData do
p1.data[j] := p2.data[k];
p1.link[j]^ := p2.link[k]^;
inc j;
inc k;
end
p1.link[j]^ := p2.link[k]^;
p1.nData := j;
dec page.nData;
deletePage(p2^);
if page.nData >= nDATA /2 then
return OK;
else
return BALANCE;
end
else
int j := p2.nData + 1;
p2.link[j]^ := p2.link[j - 1]^;
dec j;
while j > 0 do
p2.data[j] := p2.data[j - 1];
p2.link[j]^ := p2.link[j - 1]^;
dec j;
end
p2 .data[j] := page.data[i - 1];
p2 .link[j]^ := p1 .link[p1.nData]^;
page.data[i - 1] := p1 .data[p1.nData - 1];
inc p2.nData;
dec p1.nData;
return OK;
end
default:
PAGE p1^ := page.link[i]^;
PAGE p2^ := page.link[i + 1]^;
if p1.nData + p2.nData + 1 <= nDATA then
int j := p1.nData;
p1.data[j]:= page.data[i];
inc j;
int k := 0;
while k < p2.nData do
p1.data[j] := p2.data[k];
p1.link[j]^ := p2.link[k]^;
inc j;
inc k;
end
p1.link[j]^ := p2.link[k]^;
p1.nData := j;
deletePage(p2^);
page.data[i] := page.data[i + 1];
inc i;
while i + 1 < page.nData do
page.data[i] := page.data[i + 1];
page.link[i]^ := page.link[i + 1]^;
inc i;
end
page.link[i]^ := page.link[i + 1]^;
dec page.nData;
if page.nData >= nDATA /2 then
return OK;
else
return BALANCE;
end
else
int j := p1.nData;
p1 .data[j] := page.data[i];
inc j;
p1 .link[j]^ := p2 .link[0]^;
inc p1.nData;
page.data[i] := p2 .data[0];
int k := 0;
while k + 1 < p2.nData do
p2.data[k] := p2.data[k + 1];
p2.link[k]^ := p2.link[k + 1]^;
inc k;
end
p2.link[k]^ := p2.link[k + 1]^;
dec p2.nData;
return OK;
end
end
end
int delete(PAGE page^^, int data, int isRoot) is
if page^ = NULL then
return NOT_FOUND;
else
int i := 0;
int j := page.nData;
while i < j do
int m := (i + j) /2;
int c := comp(data, page.data[m]);
select
case c = 0:
select
case page.link[m]^ = NULL:
int k := m;
while k + 1 < page.nData do
page.data[k] := page.data[k + 1];
page.link[k]^ := page.link[k + 1]^;
inc k;
end
page.link[k]^ := page.link[k + 1]^;
page.nData := k;
if page.nData < nDATA / 2 then
if page.nData = 0 & isRoot != 0 then
page^ := NULL;
return OK;
else
return BALANCE;
end
end
return OK;
default:
PAGE p2^ := page.link[m]^;
while p2.link[p2.nData]^ != NULL do
p2^ := p2.link[p2.nData]^;
end
page.data[m] := p2.data[p2.nData - 1];
data := p2.data[p2.nData - 1];
i := m;
exit
end
case c < 0:
j := m;
default:
i := m + 1;
end
end
int result := delete(page.link[i]^^, data, 0);
if result > OK then
result := balance(page^, i);
end
if result > OK then
if page.nData = 0 & isRoot != 0 then
page^ := page.link[0]^;
result := OK;
end
end
return result;
end
end
Поддержание сбалансированности обычного двоичного дерева тоже возможно.
По-видимому, самый первый алгоритм был предожен Адельсоном-Вельским
и Ландисом (Г. М. Адельсон-Вельский, Е. М. Ландис, Один алгоритм организации
информации, Докл. АН СССР, 1962, том 146, номер 2, 263–266). Идея алгоритма
состоит в принятии допустимости приблизительной сбалансированности дерева.
Высоты правой и левой ветвей любого поддерева не обязаны совпадать,
но не должны отличатся более, чем на единицу.
Ниже приведен очень многословный, но, возможно, простой для понимания
вариант реализации. В комментариях приведены картинки всех возможных
преобразований поддеревьев. Узлы обозначены латинскими буквами,
поддеревья - греческими (al - alpha, bt - beta и т.д.). Знаки вопроса
означают, что высота одного из поддеревьев может быть на единицу меньше,
чем это изображено.
Можно заметить, что функции балансировки дерева при удалении
мало отличаются от функций балансировки дерева при вставке и можно
использовать только одну пару.
Компилятор - Context3, ревизия не ниже 76. Версия 00.01.00 не может быть
использована из-за ошибки в реализации оператора switch.
struct NODE
int data;
int balance;
NODE left^;
NODE right^;
end
define nNODE 0x80000;
NODE nodes[nNODE];
NODE free^ := NULL;
void initNodes() is
int i := nNODE;
while i > 0 do
dec i;
nodes[i].right^ := free^;
free^ := nodes[i]^;
end
end
int comp(int p1, int p2) is // функция сравнения ключей
select
case p1 < p2:
return -1;
case p1 > p2:
return 1;
end
return 0;
end
NODE createNode()^ is
NODE temp^ := free^;
if temp^ != NULL then
free^ := free.right^;
return temp^;
end
return NULL;
end
void deleteNode(NODE node^) is
node.right^ := free^;
free^ := node^;
end
NODE root^ := NULL;
int balanceLeft (NODE node^^) is
NODE x^ := node^;
switch x.balance of
case 1:
x.balance := 0;
return 0;
case 0:
x.balance := -1;
return 1;
default:
NODE y^ := x.left^;
switch y.balance of
case -1:
/* x(-1-old, -2-new) //al < y < bt < x < gm
/ \
y(-1)\
/ \ gm
/ bt
al
|
v
y
/ \
/ x
/ / \
al bt gm*/
//NODE al^ := y.left^;
NODE bt^ := y.right^;
//NODE gm^ := x.right^;
node^ := y^;
y.balance := 0;
y.right^ := x^;
x.balance := 0;
x.left^ := bt^;
return 0;
case 0:
/* x(-1-old, -2-new) //al < y < bt < x < gm
/ \
y(0) gm
/ \
al bt*/
/*y.balance = 0 возможно только при отсутствии увеличения высоты дерева (в этом
случае функция балансировки не вызывается) или при добавлении нового узла
(al = bt = NULL, в этом случае старое значение x.balance не может быть -1).
return 0;*/
/*Если же не принимать в расчет приведенное соображение,
то можно написать следующее:*/
/* x(-1-old, -2-new) //al < y < bt < x < gm
/ \
y(0) gm
/ \
al bt
|
v
y
/ \
/ x
/ / \
al / gm
bt*/
//NODE al^ := y.left^;
NODE bt^ := y.right^;
//NODE gm^ := x.right^;
node^ := y^;
y.balance := 1;
y.right^ := x^;
x.balance := -1;
x.left^ := bt^;
return 1;
default:
/* x(-1-old, -2-new) //al < y < bt < z < gm < x < dl
/ \
y(1) \
/ \ \
/ z(?) \
/ / \ dl
al ? ? //al length unchanged
bt gm
|
v
z
/ \
/ \
/ \
y x
/ \ / \
/ ? ? \
al bt gm dl*/
//NODE al^ := y.left^;
NODE z^ := y.right^;
NODE bt^ := z.left^;
NODE gm^ := z.right^;
//NODE dl^ := z.right^;
node^ := z^;
z.left^ := y^;
z.right^ := x^;
y.right^ := bt^;
x.left^ := gm^;
switch z.balance of
case -1:
z.balance := 0;
y.balance := 0;
x.balance := 1;
case 0:
z.balance := 0;
y.balance := 0;
x.balance := 0;
default:
z.balance := 0;
y.balance := -1;
x.balance := 0;
end
return 0;
end
end
end
int balanceRight(NODE node^^) is
NODE x^ := node^;
switch x.balance of
case -1:
x.balance := 0;
return 0;
case 0:
x.balance := 1;
return 1;
default:
NODE y^ := x.right^;
switch y.balance of
case 1:
/* x(1-old, 2-new) //al < x < bt < y < gm
/ \
/ y(1)
al / \
bt \
gm
|
v
y
/ \
x \
/ \ \
al bt gm*/
//NODE al^ := x.left^;
NODE bt^ := y.left^;
//NODE gm^ := y.right^;
node^ := y^;
y.balance := 0;
y.left^ := x^;
x.balance := 0;
x.right^ := bt^;
return 0;
case 0:
/* x(1-old, 2-new) //al < x < bt < z < gm < y < dl
/ \
al y(0)
/ \
bt gm*/
/*y.balance = 0 возможно только при отсутствии увеличения высоты дерева (в этом
случае функция балансировки не вызывается) или при добавлении нового узла
(bt = gm = NULL, в этом случае старое значение x.balance не может быть 1).
return 0;*/
/*Если же не принимать в расчет приведенное соображение,
то можно написать следующее:*/
/* x(1-old) //al < x < bt < y < gm
/ \
/ y(1)
al / \
bt \
gm
|
v
y
/ \
x \
/ \ \
al \ gm
bt*/
//NODE al^ := x.left^;
NODE bt^ := y.left^;
//NODE gm^ := y.right^;
node^ := y^;
y.balance := -1;
y.left^ := x^;
x.balance := 1;
x.right^ := bt^;
return 1;
default:
/* x(1-old, 2-new) //al < x < bt < z < gm < y < dl
/ \
/ y(-1-new)
/ / \
/ z(?)\
al / \ \
? ? dl
bt gm
|
v
z
/ \
/ \
/ \
x y
/ \ / \
/ ? ? \
al bt gm dl*/
//NODE al^ := x.left^;
NODE z^ := y.left^;
NODE bt^ := z.left^;
NODE gm^ := z.right^;
//NODE dl^ := y.right^;
node^ := z^;
z.left^ := x^;
z.right^ := y^;
x.right^ := bt^;
y.left^ := gm^;
switch z.balance of
case -1:
z.balance := 0;
x.balance := 0;
y.balance := 1;
case 0:
z.balance := 0;
x.balance := 0;
y.balance := 0;
default:
z.balance := 0;
x.balance := -1;
y.balance := 0;
end
return 0;
end
end
end
int insert(NODE node^^, int data) is
if node^ = NULL then
NODE temp^ := createNode()^;
if temp^ = NULL then
return -1;
end
temp.data := data;
temp.balance := 0;
temp.left^ := NULL;
temp.right^ := NULL;
node^ := temp^;
return 1;
else
select
case comp(data, node.data) = 0:
return -1;
case comp(data, node.data) < 0:
int result := insert(node.left^^, data);
if result > 0 then
result := balanceLeft (node^^);
end
return result;
default:
int result := insert(node.right^^, data);
if result > 0 then
result := balanceRight(node^^);
end
return result;
end
end
end
int balanceLeftOnDel(NODE node^^)
NODE x^ := node^;
switch x.balance of
case -1:
x.balance := 0;
return -1;
case 0:
x.balance := 1;
return 0;
default:
NODE y^ := x.right^;
switch y.balance of
case 1:
/* x(1-old, 2-new) //al < x < bt < y < gm
/ \
/ y(1)
al / \
bt \
gm
|
v
y
/ \
x \
/ \ \
al bt gm*/
//NODE al^ := x.left^;
NODE bt^ := y.left^;
//NODE gm^ := y.right^;
node^ := y^;
y.balance := 0;
y.left^ := x^;
x.balance := 0;
x.right^ := bt^;
return -1;
case 0:
/* x(1-old, 2-new) //al < x < bt < y < gm
/ \
/ y(0-new)
al / \
/ \
bt gm
|
v
y
/ \
x \
/ \ \
al \ gm
bt*/
//NODE al^ := x.left^;
NODE bt^ := y.left^;
//NODE gm^ := y.right^;
node^ := y^;
y.balance := -1;
y.left^ := x^;
x.balance := 1;
x.right^ := bt^;
return 0;
/* x(1-old, 2-new) //al < x < bt < y < gm < z < dl
/ \
/ y(0-new)
/ / \
/ / z(?)
al / / \
/ ? ?
bt gm dl
|
v
y
/ \
/ \
/ \
/ \
x z
/ \ / \
/ \ ? ?
al \ gm dl
bt
//NODE al^ := x.left^;
NODE bt^ := y.left^;
NODE z^ := y.right^;
//NODE gm^ := z.left^;
//NODE dl^ := z.right^;
node^ := y^;
y.balance := -1;
y.left^ := x^;
//y.right^ := z^;
x.balance := 1;
x.right^ := bt^;
return 0;*/
default:
/* x(1-old, 2-new) //al < x < bt < z < gm < y < dl
/ \
/ y(-1-new)
/ / \
/ z(?)\
al / \ \
? ? dl
bt gm
|
v
z
/ \
/ \
/ \
x y
/ \ / \
/ ? ? \
al bt gm dl*/
//NODE al^ := x.left^;
NODE z^ := y.left^;
NODE bt^ := z.left^;
NODE gm^ := z.right^;
//NODE dl^ := y.right^;
node^ := z^;
z.left^ := x^;
z.right^ := y^;
x.right^ := bt^;
y.left^ := gm^;
switch z.balance of
case -1:
z.balance := 0;
x.balance := 0;
y.balance := 1;
case 0:
z.balance := 0;
x.balance := 0;
y.balance := 0;
default:
z.balance := 0;
x.balance := -1;
y.balance := 0;
end
return -1;
end
end
end
int balanceRightOnDel(NODE node^^)
NODE x^ := node^;
switch x.balance of
case 1:
x.balance := 0;
return -1;
case 0:
x.balance := -1;
return 0;
default:
NODE y^ := x.left^;
switch y.balance of
case -1:
/* x(-1-old, -2-new) //al < y < bt < x < gm
/ \
y(-1)\
/ \ gm
/ bt
al
|
v
y
/ \
/ x
/ / \
al bt gm*/
//NODE al^ := y.left^;
NODE bt^ := y.right^;
//NODE gm^ := x.right^;
node^ := y^;
y.balance := 0;
y.right^ := x^;
x.balance := 0;
x.left^ := bt^;
return -1;
case 0:
/* x(-1-old, -2-new) //al < y < bt < x < gm
/ \
y(0) \
/ \ gm
/ \
al bt
|
v
y
/ \
/ x
/ / \
al / gm
bt*/
//NODE al^ := y.left^;
NODE bt^ := y.right^;
//NODE gm^ := x.right^;
node^ := y^;
y.balance := 1;
y.right^ := x^;
x.balance := -1;
x.left^ := bt^;
return 0;
/* x(-1-old, -2-new) //al < z < bt < y < gm < x < dl
/ \
y(0) \
/ \ \
z(?) \ \
/ \ \ dl
? ? \
al bt gm
|
v
y
/ \
/ \
/ \
/ \
z x
/ \ / \
? ? / \
al bt / dl
gm
NODE z^ := y.left^;
//NODE al^ := z.left^;
//NODE bt^ := z.right^;
NODE gm^ := y.right^;
//NODE dl^ := x.right^;
node^ := y^;
y.balance := 1;
//y.left^ := z^;
y.right^ := x^;
x.balance := -1;
x.left^ := gm^;
//x.right := dl^;
return 0;*/
default:
/* x(-1-old, -2-new) //al < y < bt < z < gm < x < dl
/ \
y(1) \
/ \ \
/ z(?) \
/ / \ dl
al ? ?
bt gm
|
v
z
/ \
/ \
/ \
y x
/ \ / \
/ ? ? \
al bt gm dl*/
//NODE al^ := y.left^;
NODE z^ := y.right^;
NODE bt^ := z.left^;
NODE gm^ := z.right^;
//NODE dl^ := x.right^;
node^ := z^;
z.left^ := y^;
z.right^ := x^;
y.right^ := bt^;
x.left^ := gm^;
switch z.balance of
case -1:
z.balance := 0;
y.balance := 0;
x.balance := 1;
case 0:
z.balance := 0;
y.balance := 0;
x.balance := 0;
default:
z.balance := 0;
y.balance := -1;
x.balance := 0;
end
return -1;
end
end
end
int delete(NODE node^^, int data) is
if node^ = NULL then
return 1;
else
select
case comp(data, node.data) = 0:
select
case node.balance < 0:
NODE max^ := node.left^;
while max.right^ != NULL do
max^ := max.right^;
end
node.data := max.data;
int result := delete(node.left^^, max.data);
if result < 0 then
result := balanceLeftOnDel(node^^);
end
return result;
case node.right^ != NULL:
NODE min^ := node.right^;
while min.left^ != NULL do
min^ := min.left^;
end
node.data := min.data;
int result := delete(node.right^^, min.data);
if result < 0 then
result := balanceRightOnDel(node^^);
end
return result;
default:
deleteNode(node^);
//INFO Omitted
node^ := NULL;
return -1;
end
case comp(data, node.data) < 0:
int result := delete(node.left^^, data);
if result < 0 then
result := balanceLeftOnDel(node^^);
end
return result;
default:
int result := delete(node.right^^, data);
if result < 0 then
result := balanceRightOnDel(node^^);
end
return result;
end
end
end