Автор: Пользователь скрыл имя, 22 Июня 2013 в 14:01, курсовая работа
Написать программу, реализующую структуру данных АВЛ-деревья. Программа должна выполнять следующие функции:
Создание дерева.
Добавление узла в дерево.
Удаление узла из дерева.
Поиск узла по значению.
Поиск минимального по значению узла.
Поиск максимального по значению узла.
Восстановление баланса дерева.
Поиск упорядоченного бинарного дерева поиска максимальной высоты.
Вывод дерева на экран в виде рисунка.
Написать программу, реализующую структуру данных АВЛ-деревья. Программа должна выполнять следующие функции:
Программа должна быть реализована на Windows Forms в среде программирования C++ Builder 6.
Одним из направлений в разработке алгоритмов поиска данных является организация поиска данных, имеющих древовидную структуру. Анализ деревьев только с точки зрения представления данных в виде иерархической структуры, позволяет сделать вывод о том, что существенного выигрыша при организации поиска не получится. Сравнение ключа поиска с ключами узлов дерева необходимо провести для всех элементов дерева.
Уменьшить число сравнений ключей с эталоном возможно, если выполнить организацию дерева особым образом, то есть расположить его элементы по определенным правилам. При этом в процессе поиска можно будет просматривать не все дерево, а отдельные поддеревья. На практике для поиска используются некоторые разновидности бинарных деревьев, которые позволяют эффективно решать задачу поиска в тех случаях, когда данные меняют свою исходную структуру в процессе обработки. Рассмотрим двоичные деревья поиска.
Двоичным деревом поиска
(ДДП) называют дерево, все вершины
которого упорядочены, каждая вершина
имеет не более двух потомков (назовём
их левым и правым), и все вершины,
кроме корня, имеют родителя. Вершины,
не имеющие потомков, называются листами.
Подразумевается, что каждой вершине
соответствует элемент или
Двоичное дерево может
быть логически разбито на уровни.
Корень дерева является нулевым уровнем,
потомки корня – первым уровнем,
их потомки – вторым, и т.д. Глубина
дерева это его максимальный уровень.
Понятие глубины также может
быть описано в терминах пути, то
есть глубина дерева есть длина самого
длинного пути от корня до листа, если
следовать от родительской вершины
до потомка. Каждую вершину дерева можно
рассматривать как корень поддерева,
которое определяется данной вершиной
и всеми потомками этой вершины,
как прямыми, так и косвенными.
Поэтому о дереве можно говорить
как о рекурсивной структуре.
Эффективность поиска по дереву напрямую
связана с его
ДДП может быть использовано для реализации таких абстракций, как сортированный список, словарь (набор соответствий "ключ-значение"), очередь с приоритетами и так далее.
При реализации дерева помимо значения ключа (key) и данных также хранятся три указателя: на родителя (net), левого (left) и правого (right) потомков. Если родителя или потомка нет, то указатель хранит нулевое (NULL, NIL) значение.
Если x – это произвольная вершина в ДДП, а вершина y находится в левом поддереве вершины x, то y.key <= x.key. Если x – это произвольная вершина ДДП, а вершина y находится в правом поддереве вершины x, то y.key >= x.key. Из свойства следует, что если y.key == x.key, то вершина y может находиться как в левом, так и в правом поддереве относительно вершины x.
Необходимо помнить, что при наличии нескольких вершин с одинаковыми значениями ключа некоторые алгоритмы не будут работать правильно. Например, алгоритм поиска будет всегда возвращать указатель только на одну вершину. Эту проблему можно решить, храня элементы с одинаковыми ключами в одной и той же вершине в виде списка. В таком случае мы будем хранить в одной вершине несколько элементов, но данный случай в статье не рассматривается.
Это двоичное дерево поиска:
Рис. 2.1.
А это нет:
Рис. 2.2.
Есть три способа обхода: Прямой (preorder), Поперечный (inorder), Обратный (postorder).
На рисунке 3 порядок обхода вершин указан номерами, при этом предполагается, что сами вершины расположены так, что образуют ДДП.
Рис. 2.3.
Наиболее часто употребляется поперечный обход, так как во всех других способах обхода следующие друг за другом вершины не связаны никакими условиями отношения.
Идея поиска проста. Алгоритм поиска в ДДП по своей природе рекурсивен. При его описании проще всего использовать понятие поддерева. Поиск начинается с корня дерева, который принимается за корень текущего поддерева, и его ключ сравнивается с искомым. Если они равны, то, очевидно, поиск закончен. Если ключ, который мы ищем, оказался больше текущего, то, очевидно, что нужная вершина находится в правом поддереве, иначе – в левом. Далее эта операция повторяется для правого или левого поддерева. В условном коде это можно описать так:
Рекурсивно:
TreeSearch(node, key)
Begin
// Если вершина равна NIL, то нечего в ней искать. Так же и возвращаем.
// Это нужно для поиска по не существующим потомкам
If (node == NIL) Then
Return node;
// Если нашли, то возвращаем указатель на найденную вершину.
If (node.key == key) Then
Return node;
// Если ключ найденной вершины больше того, который мы ищем
If (node.key > key) Then
// То искать в левом поддереве
Return TreeSearch(node.left, key);
Else
// Иначе в правом поддереве
Return TreeSearch(node.right, key);
End
Итеративно:
TreeSearch(node,key)
Begin
// Пока ещё есть вершины среди которых можно искать
//(мы просматриваем не все, но несколько) и пока мы не нашли
While (node != NIL) and (node.key != key) Do
Begin
// Если ключ найденногй вершины больше того который мы ищем
If (node.key > key) Then
node = node.left; // То искать в левом поддереве
Else
node = node.right; // А иначе в правом поддереве
End
Return node; // Возвратить найденное
End
Вершины с минимальным и максимальным значением ключа можно найти, пройдясь по левым (правым) указателям от корня (пока не достигнем NIL). Возвращаемое значение – это указатель на вершину с минимальным (максимальным) значением ключа.
TreeMinimum(node)
Begin
While (node.left != NIL) Do // Пока есть левый потомок
Node = node.left; // Перейти к нему
Return node;
End
TreeMaximum(node)
Begin
While (node.right != NIL) Do // Пока есть правый потомок
node = node.right; // Перейти к нему
Return node;
End
Добавление вершины в ДДП сопряжено с некоторыми проблемами. После добавления ДДП должно сохранить свойство упорядоченности, а это значит, что вершину, куда попало добавлять нельзя. Поэтому, прежде чем вставлять вершину, необходимо подобрать для неё подходящее место, то есть такое место, после вставки в которое, дерево сохранит своё свойство упорядоченности. Говоря другими словами, нам нужно место после вершины с наибольшим ключом из всех меньших данного.
TreeInsert(Tree,node)
Begin
nodeParent = NIL;
nodeTemp = T.root;
// Пока ещё есть вершины которые надо просмотреть, то
// есть пока мы не добрались до “листочков” дерева
While (nodeTemp != NIL) Do
Begin
nodeParent = nodeTemp;
// Если ключ вершины, которую мы хотим вставить,
// меньше ключа текущей вершины
If (node.key < nodeTemp.key) Then
nodeTemp = nodeTemp.left; // То он должен быть в его левом поддереве
Else
nodeTemp = nodeTemp.right; // А иначе в правом
End
node.nodeParent = nodeParent;
If (nodeParent == NIL) Then // Если в дереве ещё нет вершин
Tree.root = node; // То добавить первую
Else
Begin
// Если ключ вершины, которую мы хотим вставить,
// меньше ключа вершины, потомком которой должна стать
// вставляемая вершина
If (node.key < nodeParent.key) Then
nodeParent.left = node; // То добавить в дерево как левого потомка
Else
nodeParent.right = node; // Иначе добавить в дерево как правого потомка
End
End
Проблемы возникают и
при удалении. Нам необходимо сохранить
свойство упорядоченности ДДП. При
удалении возможны три случая: у
удаляемой вершины нет
TreeDelete(Tree,node)
Begin
// Если потомков не более одного (случаи 1 и 2)
If (node.left == NIL) or (node.right == NIL) Then
del = node; // физически удаляем текущую вершину
Else
del = TreeNext(node); // Иначе следующую
If (del.left != NIL) Then // Пытаемся найти хоть одного потомка
nodeTemp = del.left;
Else
nodeTemp = del.right;
// Если есть, родителем потомка делаем родителя
// удаляемой вершины (случай 2)
If (nodeTemp != NIL) Then
nodeTemp.nodeParent = del.nodeParent;
// Если удаляем корень дерева, надо указать новый корень дерева
If (del.nodeParent == NIL) Then
Tree.root = nodeTemp;
Else
Begin
// Указываем родителю удаляемой вершины качестве потомка
// потомок удаляемой вершины
If (del.nodeParent.left == del) Then
del.nodeParent.left = nodeTemp;
Else
del.nodeParent.right = nodeTemp;
End
If (del != node) Then // Если случай 3
Begin
node.key = del.key; // Скопировать ключ
{ копирование дополнительных данных }
End
Return del;
End
Случайные деревья поиска представляют собой упорядоченные бинарные деревья поиска, при создании которых элементы (их ключи) вставляются в случайном порядке.
При создании таких деревьев
используется тот же алгоритм, что
и при добавлении вершины в
бинарное дерево поиска. Будет ли созданное
дерево случайным или нет, зависит
от того, в каком порядке поступают
элементы для добавления. Примеры
различных деревьев, создаваемых
при различном порядке
При поступлении элементов в случайном порядке получаем дерево с минимальной высотой h (рис. 2.4А), при этом минимизируется время поиска элемента в дереве, которое пропорционально O(log n). При поступлении элементов в упорядоченном виде (рис. 2.4В) или в порядке с единичными сериями монотонности (рис. 2.4С) происходит построение вырожденных деревьев поиска (оно вырождено в линейный список), что нисколько не сокращает время поиска, которое составляет O(n).
Рис. 2.4. Случайные деревья поиска
В худшем случае, когда дерево вырождено в линейный список, хранение данных в упорядоченном бинарном дереве никакого выигрыша в сложности операций по сравнению с массивом или линейным списком не дает. В лучшем случае, когда дерево сбалансировано, для всех операций получается логарифмическая сложность, что гораздо лучше. Идеально сбалансированным называется дерево, у которого для каждой вершины выполняется требование: число вершин в левом и правом поддеревьях различается не более чем на 1.
Информация о работе Реализовать программу для работы с типом данных ”Дерево”