Оценка трудоемкости поиска в случайном дереве
Древовидные таблицы
Явное использование структуры бинарного дерева позволяет быстро вставлять и удалять записи и производить эффективный поиск, и особенно целесообразно для организации динамических таблиц, подверженных частым вставкам и удалениям.
Рассмотрим простой метод построения дерева. Первую запись с ключом К1поместим в корень дерева. Второй ключ К2 сравним с К1. Если К2 < К2, поместим его в левое поддерево, а если К2 ³ К1, то в правое поддерево. В общем случае, когда требуется поместить в дерево i-йключ, поступаем так: сравниваем Кi с ключами, начиная с корня. Если Кi меньше очередного ключа, то переходим к левому сыну, в противном случае – к правому, а если соответствующий сын отсутствует, то это и есть место, куда нужно вставить ключ Ki. Пусть структура узла дерева:
struct NODE {
void *Record; // указатель на запись таблицы
NODE *Left, *Right;
};
Пусть int Cmp(const void *Record1, const void *Record2) –функция, выполняющая сравнение ключей записей *Record1 и *Record2.Функция традиционно возвращает значения <0,0,>0 соответственно в случаях ключ1<ключ2, ключ1=ключ2иключ1>ключ2.
Функция, выполняющая вставку новой записи в древовидную таблицу, приведена ниже:
const int LEFT=0;
const int RIGHT=1;
//----------------------------------
NODE *InsertRecord(NODE *Root, void *NewRecord){
NODE *x,*Cur,*Next;
int WhatSon; // каким сыном устанавливать новую запись –
// - левым (LEFT) или правым (RIGHT)
int CmpKeys; // результат сравнения ключей
x=new NODE;
x->Record=NewRecord;
Cur=Root;
// поиск места вставки
for(;;){
CmpKeys=Cmp(NewRecord,Cur->Record);
switch(CmpKeys){
case –1:
WhatSon=LEFT;
Next=Cur->Left;
break;
case 0:
case 1:
WhatSon=RIGHT;
Next=Cur->Right;
break;
}
if(Next==NULL){
break;
}
Cur=Next;
}
if(WhatSon==LEFT){
Cur->Left=x;
} else {
Cur->Right=x;
}
return x;
![]() |
}
Рис 41. Результат вставки ключей в бинарное дерево
На рис. 41 изображен результат работы алгоритма.
Операция удаления узла.Сначала заметим, что обход бинарного дерева в обратном порядке даёт сортированную последовательность. Все замечательные свойства древовидной таблицы связаны с этим обстоятельством. При удалении узла необходимо сохранить порядок следования узлов в обратном порядке. Легко удалить лист или узел, у которого пуста правая или левая связь. В общем случае одно из возможных решений таково: удалить следующий в обратном порядке узел, левая связь которого пуста, а затем вернуть его на место узла, который действительно требуется удалить. Ниже приведен текст функции, удаляющей узел, содержащий запись с целым ключом Key. Здесь предполагается, что дерево имеет голову, и, что само дерево является левым поддеревом головы. Голова содержит максимально возможное значение ключа. В случае успеха функция возвращает true.
struct NODE {
int Info;
NODE *Left;
NODE *Right;
int Rank; // для доступа по индексу
};
bool DeleteKey(NODE *Head, int Key){
// удалить Key из дерева с головой Head
NODE *Cur, *Next=NULL,*father=NULL;
// ищем узел, запоминая каждый раз отца
for(Cur=Head; Cur->Info!=Key && Cur!=NULL; Cur=Next){
if(Key<=Cur->Info){
Next=Cur->Left;
} else {
Next=Cur->Right;
}
father=Cur;
}
if(Cur==NULL){ // не нашли Key
return false;
}
// узел сur надо удалить
// может у Cur пуста правая связь ?
if(Cur->Right==NULL){
if(father->Left==Cur){
// он левый сын своего отца
father->Left=Cur->Left;
} else {
// он правый сын своего отца
father->Right=Cur->Left;
}
delete Cur;
return true;
}
// поищем последователя с пустой левой связью
// шаг вправо и вниз до упора по левым связям
Next=Cur->Right;
father=Cur;
while(Next->Left!=NULL){
father=Next;
Next=Next->Left;
}
// Next - удаляемый узел
if(father->Left==Next){
// удаляемый узел - левый сын своего отца
father->Left=Next->Right;
} else {
// удаляемый узел - правый сын своего отца
father->Right=Next->Right;
}
Cur->Info=Next->Info;
delete Next;
return true;
}
Обозначим значения ключей, следующих в порядке возрастания k1,k2,…,kn. Вероятность того, что в корень попадет ключ ki, равна 1/n. Тогда в левом поддереве будет i-1 узлов, а в правом n-i узлов. Пусть an – средний
![]() |
уровень узла в дереве, содержащем n узлов. Тогда при условии, что ключ ki находится в корне,
Выражение в правой части содержит три слагаемых:
1. искомый узел находится в левом поддереве с вероятностью (i-1)/n и средний уровень его равен ai-1+1
2. с вероятностью 1/n это корень и его уровень 1.
3. с вероятностью (n-i)/n искомый узел находится в правом поддереве и средний уровень его an-i+1
Искомая величина an представляет собой среднее по i для всех
(1)
Отсюда следует: (2)
а из (1) можно получить: (3)
Из (3) можно найти , и, подставив это в (2), получим:
(4)
an можно представить в виде: (5),где
. Справедливость (5) можно проверить непосредственной подстановкой (5) в (4).
По формуле Эйлера , где
- постоянная Эйлера, равная 0.571… Для больших n получим:
В заключение отметим, что, хотя среднее время поиска в случайном дереве пропорционально логарифму числа узлов, тем не менее, существует досадная
![]() |
возможность получения вырожденного дерева, время поиска в котором пропорционально числу узлов. Примеры таких деревьев приведены на рис.42.
Рис.42 Примеры вырожденных деревьев