Добавление и удаление
Далее будет рассмотрен нерекурсивный алгоритм добавления элемента в дерево. Сначала надо найти вершину, к которой в качестве потомка необходимо добавить новую вершину (фактически, произвести поиск), а затем присоединить к найденной новую вершину, содержащую значение Item (процедура написана в предположении, что добавляемого элемента в дереве нет).
procedure TreeAdd(Item: TypeElement; var Tree: PTree);
{Добавление в дерево вершины со значением Item}
var
NewNode, Current: PTree;
begin
if Tree = nil then begin {Дерево пустое}
{Создаем корень}
New(Tree);
Tree^.Data := Item;
Tree^.Left := nil;
Tree^.Right := nil;
end else begin
Current := Tree;
{Поиск вершины}
while ((Item > Current^.Data) and (Current^.Right <> nil)) or
((Item < Current^.Data) and (Current^.Left <> nil)) do
if Item > Current^.Data then
Current := Current^.Right
else
Current := Current^.Left;
{Создание новой вершины}
New(NewNode);
NewNode^.Data := Item;
NewNode^.Left := nil;
NewNode^.Right := nil;
If Item > Current^.Data then {Новая вершина больше найденной}
Current^.Right := NewNode {Присоединяем новую справа}
else {Новая вершина меньше найденной}
Current^.Left := NewNode; {Присоединяем новую слева}
end;
end;
Листинг 3.29 – Процедура добавления элемента в двоичное дерево поиска
Листинг 3.30 – Процедура добавления элемента в двоичное дерево поиска
Процедура удаления элемента будет несколько сложнее. При удалении может случиться, что удаляемый элемент находится не в листе, то есть вершина имеет ссылки на реально существующие поддеревья. Эти поддеревья терять нельзя, а присоединить два поддерева на одно освободившееся после удаления место невозможно. Поэтому необходимо поместить на освободившееся место либо самый правый элемент из левого поддерева, либо самый левый из правого поддерева. Нетрудно убедиться, что упорядоченность дерева при этом не нарушится. Договоримся, что будем заменять самый левый элемент из правого поддерева (следующий в смысле порядка на ключах, Successor).
Нельзя забывать, что при замене вершина, на которую производится замена, может иметь правое поддерево. Это поддерево необходимо поставить вместо перемещаемой вершины.
procedure TreeDelete(Item: TypeElement; var Tree: PTree);
{Удаление из дерева вершины со значением Item}
var
Parent, Cur, Cur2: PTree;
Direction: (L, R); {направление движения (влево/вправо)}
begin
{Поиск удаляемого элемента}
Parent := nil; {предок удаляемой вершины}
Cur := Tree;
while ((Item > Cur^.Data) and (Cur^.Right <> nil)) or
((Item < Cur^.Data) and (Cur^.Left <> nil)) do begin
Parent := Cur;
if Item > Cur^.Data then begin
Cur := Cur^.Right; Direction := R;
end else begin
Cur := Cur^.Left; Direction := L;
end;
end;
if Cur <> nil then begin {Вершина найдена}
{Анализируем наличие потомков у найденной вершины}
{и удаляем соответствующим образом}
if (Cur^.Left <> nil) and (Cur^.Right <> nil) then begin
{Удаление вершины в случае наличия у нее обоих потомков}
Parent := Cur; Cur2 := Cur^.Right;
{Поиск кандидатуры на замену}
while Cur2^.Left <> nil do begin
Parent := Cur2; Cur2 := Cur2^.Left;
end;
{Заменяем}
Cur^.Data := Cur2^.Data;
{Спасаем правое поддерево вершины, которой заменяем}
if Parent <> Cur then
Parent^.Left := Cur2^.Right
else
Parent^.Right := Cur2^.Right;
Dispose(Cur2);
end else begin
{Удаление вершины в случае наличия у нее}
{не более одного потомка}
if (Cur^.Left = nil) then begin
if Parent <> nil then begin
if Direction = L then
Parent^.Left := Cur^.Right
else
Parent^.Right := Cur^.Right;
end else begin
Tree := Cur^.Right;
end;
end;
if (Cur^.Right = nil) then begin
if Parent <> nil then begin
if Direction = L then
Parent^.Left := Cur^.Left
else
Parent^.Right := Cur^.Left;
end else begin
Tree := Cur^.Left;
end;
end;
Dispose(Cur);
end;
end;
end;
Листинг 3.31 – Процедура удаления элемента из двоичного дерева поиска
Листинг 3.32 – Процедура удаления элемента из двоичного дерева поиска
Рисунок 3.14 – Удаление элемента из двоичного дерева поиска
Удаление вершины z из двоичного дерева поиска. (а) Если вершина z не имеет детей её можно удалить без проблем. (6) Если вершина z имеет одного ребёнка, помещаем его на место вершины z. (в) Если у вершины z двое детей, процесс сводится к предыдущему случаю, т.е. удаляем вместо неё вершину у с непосредственно следующим значением ключа (у этой вершины ребёнок один) и помещая ключ kеу[у] (и связанные с ним дополнительные данные) на место вершины z.
Временная сложность этих алгоритмов (она одинакова для этих алгоритмов, так как в их основе лежит поиск) оценим для наилучшего и наихудшего случая. В лучшем случае, – то есть случае полного двоичного дерева – получаем сложность Omin(log n). В худшем случае дерево может выродиться в список. Такое может произойти, например, при добавлении элементов в порядке возрастания. При работе со списком в среднем придется просмотреть половину списка. Это даст сложность Omax(n).
В процедуре очистки дерева применяется обратный метод обхода дерева. Использование обратного метода обхода гарантирует, что будут сначала посещены и удалены все потомки предка, прежде чем удалится сам предок.
procedure TreeClear(var Tree: PTree);
{Очистка дерева}
begin
if Tree <> nil then begin
TreeClear(Tree^.Left);
TreeClear(Tree^.Right);
Dispose(Tree);
Tree := nil;
end;
end;
Листинг 3.33 – Процедура очистки двоичного дерева поиска
Временная сложность этой операции O(n).
- Министерство образования Российской Федерации
- Содержание
- 1.2 Скорость роста функций
- 1.3 Анализ алгоритмов; время работы в лучшем, худшем случаях и в среднем
- 1.4 Типы данных, структуры данных и абстрактные типы данных
- 1.5 Динамические множества
- 2 Алгоритмы сортировок
- 2.1 Понятие внутренней и внешней сортировки
- 2.2 Сортировка вставками
- 2.3 Сортировка слиянием
- 2.3.1 Описание алгоритма
- 2.3.2 Анализ времени работы алгоритмов «разделяй и властвуй»
- 2.3.2 Анализ времени работы сортировки слиянием через рекуррентное соотношение
- 2.3.3 Анализ времени работы сортировки слиянием через геометрическую интерпретацию
- 2.4 Пирамидальная сортировка
- 2.4.1 Введение в алгоритм
- 2.4.2 Сохранение основного свойства кучи
- 2.4.3 Построение кучи
- 2.5 Быстрая сортировка
- 2.5.1 Введение в алгоритм
- 2.5.2 Описание
- 2.5.3 Разбиение массива
- 2.5.4 Особенности работы быстрой сортировки
- 2.6 Особенности реализации алгоритмов сортировки; сортировка за линейное время
- 2.6.1 Введение
- 2.6.2 Разрешающее дерево сортировки сравнениями
- 2.7 Цифровая сортировка
- 2.8 Сортировка вычерпыванием
- 2.8.1 Описание алгоритма
- 2.8.2 Вероятностный анализ времени работы сортировки вычерпыванием
- 2.8.3 Анализ времени работы сортировки вычерпыванием через геометрическую интерпретацию
- 2.9 Сортировка подсчетом
- 2.9.1 Описание алгоритма
- 2.9.2 Анализ времени работы
- 3 Элементарные и нелинейные структуры данных
- 3.1 Элементарные структуры: список, стек, очередь, дек
- 3.1.1 Список Линейный однонаправленный список
- Линейный двунаправленный список
- Двунаправленный список с фиктивными элементами
- Циклические списки
- Циклический однонаправленный список
- Циклический двунаправленный список
- 3.1.2 Стек
- 3.1.3 Очередь
- 3.1.3 Дек
- 3.2 Нелинейные структуры данных
- 3.2.1 Представление корневых деревьев в эвм
- Обходы деревьев
- 3.2.2 Двоичные деревья Спецификация двоичных деревьев
- Реализация
- Обходы двоичных деревьев
- 3.2.3 Двоичные деревья поиска Основные операции
- Минимум и максимум
- Следующий и предыдущий элементы
- Добавление и удаление
- Случайные деревья поиска
- Оптимальные деревья поиска
- 4 Хеширование
- 4.1 Введение
- 4.2 Прямая адресация; таблицы с прямой адресацией
- 4.3 Хеш – таблицы; возникновение коллизий и их разрешение
- Разрешение коллизий с помощью цепочек
- Анализ хеширования с цепочками
- 4.4 Способы построения хеш – функций Выбор хорошей хеш-функции
- Ключи как натуральные числа
- Деление с остатком
- Умножение
- Универсальное хеширование
- 4.5 Открытая адресация; способы вычисления последовательности испробованных мест: линейная последовательность проб, квадратичная последовательность проб, двойное хеширование
- Линейная последовательность проб
- 1 / (1 – )
- 5 Основные принципы разработки алгоритмов
- 5.1 Введение в теорию графов
- 5.1.1 Графы
- 5.1.2 Представление графов
- 5.2 Алгоритмы на графах: поиск в ширину, поиск в глубину
- 5.2.1 Поиск в ширину (волновой алгоритм)
- 5.2.2 Анализ поиска в ширину
- 5.2.3 Деревья поиска в ширину
- 5.2.4 Поиск в глубину
- 5.2.5 Анализ поиска в глубину
- 5.2.6 Свойства поиска в глубину
- 5.2.7 Классификация рёбер
- 5.3 Топологическая сортировка, задача о разбиении графа на сильно связанные компоненты
- 5.3.1 Топологическая сортировка
- 5.3.2 Сильно связные компоненты
- 5.4 Алгоритм построения минимального остовного дерева
- 5.4.1 Остовные деревья минимальной стоимости
- 5.4.2 Построение минимального покрывающего дерева
- 5.4.3 Алгоритмы Крускала и Пpимa
- 5.4.4 Алгоритм Крускала
- 5.4.5 Алгоритм Прима
- 5.5 Задача нахождения кратчайших путей на графах; алгоритм Флойда; алгоритм Дейкстры
- 5.5.1 Нахождение кратчайшего пути
- 5.5.2 Алгоритм Дейкстры
- 5.5.3 Алгоритм Флойда
- 5.6 Поиск с возвратом
- 5.6.1 Введение
- 5.6.2 Переборные алгоритмы
- 5.6.3 Метод ветвей и границ
- 5.6.4 Метод альфа-бета отсечения
- 5.6.5 Локальные и глобальные оптимальные решения
- 5.7 Метод декомпозиции ( «Разделяй и властвуй»)
- 5.7.1 «Ханойские башни»
- 5.8 Жадные алгоритмы и динамическое программирование
- 5.8.1 Задача о выборе заявок
- 5.8.2 Дискретная задача о рюкзаке
- 5.8.3 Непрерывная задача о рюкзаке
- 5.8.4 Числа Фибоначчи
- 5.8.5 Задача триангуляции многоугольника
- 5.8.6 Дп, жадный алгоритм или что-то другое?