Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Пособие часть1.doc
Скачиваний:
22
Добавлен:
01.03.2025
Размер:
6.94 Mб
Скачать

Удаление

Удаление узлов выполняется несколько сложнее, чем поиск и вставка, поскольку новый узел можно всегда вставлять в качестве листа, но удалять приходится не только листья, но и внутренние узлы. Рассмотрим 3 основных ситуации.

1. Удаляется лист. Это самый простой случай, т. к. достаточно лишь обнулить соответствующую ссылку у родителя и, конечно, освободить память (это действие обязательно во всех случаях).

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

3. Последний случай является самым сложным. У удаляемого внутреннего узла есть оба сына, например, из дерева на рис.5.3,а нужно удалить корень.

Рис.5.3. Удаления корня из бинарного дерева поиска (два варианта)

Ни один из сыновей удаляемого корня не сможет занять его место, не нарушив упорядоченности дерева. Но все же есть два варианта решения этой задачи, изображенные на рис.5.3,б и 5.3,в. В первом случае это самый последний правый сын из левого поддерева, во втором, наоборот, самый последний левый сын, но из правого поддерева. Оба решения вполне логичны, на самом деле, это два самых близких значения к ключу корня (одно немного меньше, другое немного больше, а при наличии повторяющихся значений вторым способом будет найден дубликат). При реализации для определенности можно следовать любому из двух вариантов.

Несмотря на разветвленную логику алгоритма удаления, количество перемещений по дереву по-прежнему не превышает его высоту. Это значит, что мы опять имеем линейную зависимость от высоты дерева.

Итак, последний вывод — бинарное дерево поиска с хорошей степенью плотности позволяет полностью избежать медленных операций с линейной сложностью от количества узлов дерева, при этом все операции имеют линейную сложность от высоты.

Теперь можно перейти к реализации функций для работы с бинарным деревом поиска.

5.4.3. Реализация бинарного дерева поиска

В подавляющем большинстве приложений используется ссылочная реализация дерева на основе указателей. Структура узла бинарного дерева в этом случае содержит, кроме ключа и связанных с ним данных, указатели на правого и левого сына. Поэтому введем новую структуру узла дерева node, которая содержит в качестве данных уже используемую ранее структуру item и два указателя.

Определим тип bst (binary seach tree — бинарное дерево поиска),

typedef node* bst;

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

В листинге 5.3 содержится рекурсивный и нерекурсивный варианты реализации функций поиска и вставки и нерекурсивный вариант функции удаления (рекурсивный можно найти в [14]). Для полноты картины приведена функция ЛКП-обхода, которая выводит элементы дерева в порядке возрастания ключей, и функция, которая строит бинарное дерево поиска, заполненное случайными значениями, вызывая при этом функцию вставки.

Листинг 5.3. Реализация бинарного дерева поиска

#include <iostream.h>

#include <stdlib.h>

typedef int T_key; //тип ключа, может быть любым

typedef char T_data;//тип связанных данных, любой

struct item //структура элемента

{ T_key key; //ключ

T_data data; //связанные данные

};

const item nullitem={-1};//пустой элемент возвращается при промахе поиска

struct node // узел дерева

{item data; // данные

node *left, *right; // указатели на детей

node(item x) // конструктор для заполнения узла при создании

{data=x;left=right=NULL;}

};

typedef node* bst; //bst - binary seach tree

// ниже приводится реализация функций

// нерекурсивная функция поиска

item seach(bst root, T_key k)

{if (!root) return nullitem;

bst p=root;

while (p)

{ if (k==p->data.key) return p->data;

if (k<p->data.key) p=p->left;

else p=p->right;

}

return nullitem;

}

// рекурсивный вариант функции поиска

item seach_rec(bst root, T_key k)

{ if(!root) return nullitem; // дерево пусто - промах

if (k==root->data.key) return root->data; // поиск успешен

if (k<root->data.key) return seach_rec(root->left, k);

else return seach_rec(root->right, k);

}

// нерекурсивная функция вставки

bool insert(bst &root, item x)

{ if (!root) // дерево еще не заполнено

{ root=new node(x); if(root)return true; else return false;

}

bst p=root,parent; // parent родитель p

while (p) // находим место для вставки

{ parent=p;

if (x.key<p->data.key) p=p->left;

else p=p->right;

}

p= new node(x); // формируем новый элемент

//вставляем его как левого или правого сына

if (x.key<parent->data.key) parent->left=p;

else parent->right=p;

if (p) return true; else return false;

}

// рекурсивная функция вставки

bool insert_rec(bst &root, item x)

{ if (!root)// дерево пустое - терминальная ветвь

{ root=new node(x);if (root) return true; else return false;

}

if (x.key<root->data.key) return insert_rec(root->left,x);

else return insert_rec(root->right,x);

}

// функция удаления узла

bool remove(bst &root, T_key k)

{ if(!root) return false; // дерево пусто

bst p=root,parent=NULL;

// поиск удаляемого узла p и его родителя

while (p&&k!=p->data.key)

{ parent=p;

if (k<p->data.key) p=p->left;

else p=p->right;

}

if (!p) return false; // обработали промах

// удаляем лист

if (!p->left&&!p->right)

if(p==root) root=NULL; //может, в дереве всего один узел

else if (parent->left==p) parent->left=NULL;

else parent->right=NULL;

// удаляем узел, у которого только один сын

if (p->left&&!p->right||!p->left&&p->right)

{ bst q; // запомним указатель на сына

if (p->left) q=p->left; else q=p->right;

if(p==root) root=q; // у корня нет родителя

else // подсоединяем сына к дедушке, удаляя родителя

if (parent->left==p) parent->left=q;

else parent->right=q;

}

if (p->left&&p->right)// есть оба сына

{ //спускаемся в левое поддерево

bst t=p->left,parent=p; //parent-родитель t

while (t->right) {parent=t;t=t->right;}

//нашли крайнего правого сына t и его родителя parent

p->data=t->data; //заменили данные у удаляемого узла

// теперь удаляем крайнего правого сына t

if (!t->left) //он лист

parent->right=NULL;

else // у него есть левое поддерево

parent->right=t->left;

p=t; // теперь можно освобождать память для t

}

delete p; return true; //освободили память

}

// формирование бинарного дерева поиска из n случайных элементов

void randtree(bst &root, int n)

{ item x;

for (int i=0;i<n; i++)

{ x.key=rand()%1000; // случайные числа

x.data=rand()%26+65; // случайные заглавные латинские буквы

insert(root,x); //или insert_rec(root,x);

}

}

// вывод дерева в порядке возрастания ключей

void out(bst root)

{if (!root) return;

out(root->left);

cout<<root->data.key<<" "<<root->data.data<<"; ";

out(root->right);

}