Дерево является нелинейной динамической структурой хранения данных. Дерево состоит из узлов или вершин, которые содержат поля данных и указатели на другие узлы или вершины.
Бинарное дерево
Узел или вершина бинарного дерева содержит поле данных и два поля с указателями. Поля указателей называются левым указателем (left) и правым указателем (right), поскольку они указывают на левое и правое поддерево, соответственно. Значение NULL указателя является признаком пустого поддерева.
Корневой узел определяет входную точку дерева, а поле указателя – узел следующего уровня. Листовой узел (лист) содержит NULL в поле правого и левого указателей.
Класс TreeNode
Объекты класса TreeNode являются узлами бинарного дерева (рис.8.1).
class TreeNode
{
private:
// указатели левого и правого дочерних узлов
TreeNode* left;
TreeNode* right;
public:
// открытый элемент
int data;
// конструктор
TreeNode(const int& item, TreeNode* lptr=NULL,
TreeNode* rptr=NULL);
// функции-элементы доступа к полям указателей
TreeNode* Left() const;
TreeNode* Right() const;
};
Конструктор инициализирует поля данных и указателей. С помощью пустого указателя NULL узлы инициализируются как листья. Имея указатель P объекта класса TreeNode в качестве параметра, конструктор присоединяет P как левого или правого потомка нового узла. Функции-элементы Left() и Right() возвращают значения полей левого и правого указателей. Благодаря этому клиент класса имеет доступ к левому и правому потомкам узла.
Примеры
// указатели целочисленных узлов дерева
TreeNode*root, *lp, *rp;
TreeNode*p;
// создать листья, содержащие 20 и 30 в качестве данных
lp=new TreeNode (20);
rp=new TreeNode (30);
// создать корень, содержащий число 10 и двух потомков
root= new TreeNode (10, lp, rp);
Рис.8.1 – бинарное дерево
root->data=50;// присвоить корню 50
Переопределение конструктора
// конструктор инициализирует поля данных и указателей
Бинарное дерево поиска – это структура (рис.8.2), которая упорядочивает элементы посредством отношения “<”. Чтобы сравнить узлы дерева, подразумевают, что часть или все поле данных определено в качестве ключа и операция “<” сравнивает ключи при размещении элемента на дереве.
Бинарное дерево поиска строится по следующему правилу:
Для каждого узла значения данных в левом поддереве меньше, чем в этом узле, а в правом поддереве – больше.
Дерево называется поисковым потому, что в поисках некоторого элемента (ключа) можно идти лишь по совершенно конкретному пути. Начав с корня, сканируют левое поддерево, если значение ключа меньше текущего узла. В противном случае сканируют правое поддерево. Метод создания дерева позволяет осуществлять поиск по кратчайшему пути от корня. Бинарные деревья поиска предназначены для быстрого доступа к данным. В идеале дерево является сбалансированным и имеет высоту и эффективность поиска порядка
Рис.8.2 – бинарное упорядоченное дерево
Для обработки данных, размещенных в дереве, применяют следующие методы прохождения дерева:
левое поддерево-узел-правое поддерево, или симметричный метод;
левое поддерево-правое поддерево-узел, или обратный метод;
правое поддерево-узел- левое поддерево, или справа-налево;
узел-левое поддерево-правое поддерево, или сверху-вниз.
Для реализации методов используют рекурсивные алгоритмы.
Класс BinSTree
Объектом этого класса является бинарное упорядоченное дерево (пример - на рис.8.2).
// класс - бинарное дерево
class BinSTree
{
public:
// конструкторы и деструктор
BinSTree();
//конструктор копирования
BinSTree(const BinSTree& tree);
~BinSTree();
// оператор присваивания
BinSTree& operator=(const BinSTree& rhs);
// функции-элементы обработки данных
int Find(int& item); // есть или нет узла с item
void Insert(const int& item); // вставить узел с item
void Delete(const int& item); // удалить узел с item
int Depth(TreeNode *t); // получить глубину дерева
// получить количество листьев в дереве
void CountLeaf(TreeNode *t,int& count);
// получить корень дерева
TreeNode *GetRoot() const {return root;}
// получить количество узлов в дереве
int GetSize() const {return size;}
private:
// указатель на корень
TreeNode *root;
// количество узлов в дереве
int size;
// выделить память под узел дерева
TreeNode *GetTreeNode(const int& item,
TreeNode *lptr, TreeNode *rptr);
// удалить все узлы дерева
void DeleteTree(TreeNode* t);
// получить указатели - на узел с item и его родителя
TreeNode *FindNode(const int& item,
TreeNode* &parent)const;
};
Конструктор инициализирует данные-элементы. Конструктор копирования и перегруженный оператор присваивания с помощью функции-элемента CopyTree создают новое бинарное дерево для текущего объекта.
Алгоритм удаления узлов дерева реализован функцией-элементом DeleteTree и используется функцией-элементом ClearTree, вызываемой как деструктором, так и перегруженной операцией присваивания.
Функции-элементы Find и Insert начинают с корня и используют определение бинарного дерева поиска. Алгоритм идет по правому поддереву, когда ключ или новый элемент больше значения текущего узла. В противном случае прохождение продолжается по левому поддереву. Функция-элемент Find использует закрытую функцию-элемент FindNode, принимающую в качестве параметра ключ и осуществляющую прохождение вниз по дереву. Функция возвращает указатель на совпавший узел и указатель на его родителя. Если совпадение происходит в корне, родительский указатель равен NULL.
Функция-элемент Delete удаляет из дерева узел с заданным ключом. Сначала с помощью функции-элемента FindNode устанавливается место этого узла на дереве и определяется указатель на его родителя. Если искомый узел отсутствует, операция удаления завершается.
Удаление узла из дерева требует ряда проверок, чтобы определить, куда присоединить сыновей удаляемого узла. Поддеревья должны быть заново присоединены таким образом, чтобы сохранилась структура бинарного упорядоченного дерева.
Функция FindNode возвращает указатель DNodePtr на узел D, подлежащий удалению. Второй указатель, PNodePtr, идентифицирует узел P – родителя удаляемого узла. Функция Delete ”пытается” подыскать заменяющий узел R, который будет присоединен к родителю и, следовательно, займет место удаленного узла. Заменяющий узел R идентифицируется указателем RNodePtr.
Функция Delete реализует алгоритм поиска заменяющего узла из четырех случаев, зависящих от числа и расположения сыновей удаляемого узла. Отметим, что если указатель на родителя равен NULL, то удаляется и обновляется корень. Эта ситуация учитывается алгоритмом.
Чтобы функции-элементы класса BinSTree получили доступ к закрытым данным-элементам left и right класса TreeNode, класс BinSTree объявляется другом класса TreeNode. Это объявление предваряется формальным объявлением класса BinSTree.
Целью данной лабораторной работы является изучение алгоритмов и функций работы с бинарным упорядоченным деревом на примере дерева из целых чисел.
Проектирование приложения.
Выбор, размещение и задание свойств компонентов.
Коды классов, функций и обработчиков событий
Сохраните модуль главной формы под именем LR_8, а проект – под именем PR_LR_8.
Для размещения классов в проекте использованы модули, не связанные с формой. Класс для узла дерева объявлен и определен в файле f_8_1, а класс для бинарного дерева – в модуле f_8_2. Ниже приведены заголовочные файлы и файлы реализации этих модулей.
Достаточно полную информацию для размещения компонентов на форме и задания их свойств можно получить из представленных ниже рис.8.3, рис.8.4 и заголовочного файла модуля LR_8.
Перенесем на форму компоненты и зададим их свойствам значения. Со страницы Дополнительно – LabeledEdit1 (EditLabel->Caption – Ключ, Text - 10), диспетчер действий ActionManager1 и полосу главного меню ActionMainMenuBar1. По умолчанию полоса главного меню ActionMainMenuBar1 расположится вверху, на всю ширину формы. Задайте её свойство Align = alNone, чтобы придать ей нужные размеры и расположить в нужном месте.
Затем со страницы Win32 перенесите на форму ImageList1, со страницы Стандарт - Label1 (Caption – ДЕРЕВО), Memo1, GroupBox1 (Caption – Формирование дерева). На панель GroupBox1 перенесите: со страницы Стандарт - две радиокнопки – RadioButton1 (Caption - вручную, Checked – true, Enabled - true) и RadioButton2 (Caption - автоматически, Checked – false, Enabled - true), Label2 (Caption – количество узлов), Button1 (Caption – Добавить узел), Button2 (Caption – Удалить узел ), со страницы Примеры – CSpinEdit1 (MaxValue - 100, MinValue – 1, Value – 10), со страницы Дополнительно – BitBtn1 (Caption – ПУСК), LabeledEdit2 (EditLabel->Caption – целое число, Text - 10).
Загрузите в компонент ImageList1 пиктограммы из файлов fldropen, filesave, floppy, find, insert, clear, delete, arrow2d, erase, dooropen, directry. В компоненте ActionManager1 установим свойство Images равным ImageList1, связав тем самым диспетчер действий со списком изображений.
Добавьте на форму с помощью диспетчера действий ActionManager1 инструментальную панель ActionTollBar1, задайте для нее Align = alNone, Constraints->MaxHeight =230, Constraints->MinHeight =230 и расположите ее на форме согласно рис.8.3.
В диспетчере действий ActionManager1 создайте три категории действий: Файл, Дерево, Выход.
По категориям объектам действий даны следующие надписи (Caption) - имена (Name): Файл: Сохранить как - FileSaveAs, Сохранить - FileSave, Открыть - FileOpen; Дерево: Уничтожить дерево - ClearTree, Поиск узла по ключу - Find, Кол-во листьев - CountLeaf, Глубина дерева - Depth, Вывести дерево в окно - OutMemo, Удалить узел - Delete, Добавить узел - Insert, Очистить окно - ClearMemo. В свойство ImageIndex перечисленных объектов действий занесите соответствующие значения.
В свойство Action кнопок Button1 (Caption – Добавить узел) и Button2 (Caption – Удалить узел ) занесите соответственно значения Insert и Delete.
Действия по активации и инициализации компонентов и обработчики событий приведены в файле реализации LR_8.cpp модуля LR_8.
1.Запустите приложение на выполнение, нажав быстрые кнопки Сохранить все и Запуск.
2.Выполните тестирование по рис.8.5. Разница между заданным и фактическим количеством узлов в дереве объясняется тем, что узлы для последующих равных чисел не создаются.
3.Выполните тестирование по рис.8.6. Здесь вначале используется автоматический режим формирования дерева, а затем – ручной.
4.Создать дерево, а затем – копию этого дерева. Деревья вывести в окно.
5.Создать два дерева, а затем одно дерево присвоить другому. Правильность действий подтвердить выводом в окно.
6.Реализовать вертикальный вывод дерева в окно.
7.Реализовать сохранение дерева в файле.
8.Написать обработчики соответствующих событий по п.4…п.7 и дополнить интерфейс.
9.Полученные результаты продемонстрировать преподавателю.
Рис.8.5 – формирование дерева в автоматическом режиме
Рис.8.6 - формирование дерева в автоматическом и ручном режимах
Контрольные вопросы
1.Расскажите о назначении и содержании класса TreeNode.
2.Как выделяется и инициализируется память для узла дерева?
3.Укажите в коде использование функций Left() и Right(). Почему они объявлены константными?
4.Как выполняются конструктор и деструктор класса TreeNode?
5.Расскажите о назначении и содержании класса BinSTree.
6.С какой целью класс BinSTree объявлен другом класса TreeNode?
7.Как выполняются конструктор и деструктор класса BinSTree?
8.Как осуществляется поиск узла по ключу?
9.Как выполняется функция Find?
10.Как добавляется узел в дерево?
11.Как создать дерево в автоматическом режиме?
12.Как создать дерево в ручном режиме?
13.Как выводится дерево в окно?
14.Поясните назначение и выполнение функции FindNode.
15.Почему нужен указатель на родителя удаляемого узла?
16.Как находят заменяющий узел, когда удаляемый узел имеет оба поддерева?
17.Как происходит замена удаляемого узла, если заменяющий узел не является его сыном?
18.При выполнении какого условия заменяют корень?
19.Расскажите о назначении и выполнении функций Delete, DeleteTree, ClearTree, CopyTree, Depth, CountLeaf.
20.Как сохранить дерево в файле?
21.Как вывести дерево из файла?
22.Как выполняется перегруженная операция присваивания?
23.Как создать новое дерево – копию другого дерева?
24.Как присвоить одно дерево другому?
25.Как реализовать вертикальный вывод дерева?
Задания
1.Последовательность чисел представить в виде упорядоченного бинарного дерева. Вывести дерево и все его ветви на экран.
2.Массив из целых чисел представить в виде идеально сбалансированного бинарного дерева. Вывести дерево и слой с заданным ключом на экран.
3.Последовательность чисел представить в виде упорядоченного бинарного дерева. Вывести на экран дерево и все ветви слоя с заданным ключом.
4.Массив из чисел представить в виде идеально сбалансированного бинарного дерева. Вывести на экран дерево и ту половину дерева, где находится заданный ключ - с вершины, а другую половину - с корня.
5.Последовательность букв представить в виде идеально сбалансированного бинарного дерева. Вывести на экран дерево и все слои дерева, начиная с вершины, а затем - с корня.
6.Массив из букв представить в виде упорядоченного бинарного дерева. Вывести на экран дерево и номер того слоя, где находится заданная буква.
7.Массив из чисел представить в виде упорядоченного бинарного дерева. Вывести на экран дерево и все его ветви, исходящие из слоя с заданным ключом.
8.Последовательность чисел представить в виде упорядоченного бинарного дерева. Вывести на экран дерево и слой с минимальным ключом.
9.Последовательность чисел представить в виде упорядоченного бинарного дерева. Вывести на экран дерево и ветви, сумма ключей в которых превышает заданное число.
10.Последовательность букв представить в виде идеально сбалансированного дерева. Вывести на экран дерево и ветви с гласными буквами.
11.Массив строк представить в виде упорядоченного бинарного дерева. Вывести дерево и слои со строками одинаковой длины на экран.
12.Массив строк представить в виде идеально сбалансированного дерева. Вывести дерево, слой и ветвь со строкой максимальной длины на экран.
13.Два бинарных дерева зеркально подобны, если либо оба они пусты, либо оба непусты, и при этом левое поддерево одного из них подобно правому поддереву другого и наоборот. Определить, являются ли два дерева зеркально подобными.
14.Два бинарных дерева подобны, если либо оба они пусты, либо оба непусты, и при этом их левые и правые поддеревья подобны. Определить, являются ли два дерева подобными.
15.Составить программу, которая читает произвольный C-файл и печатает в алфавитном порядке каждую группу имен переменных, совпадающих в первых N символах, но различающихся где-либо дальше. При решении задачи использовать дерево с узлами вида: struct NODE{char* word; int k; NODE* left; NODE* right;};. (Слова внутри символьных строк и комментариев не рассматривать.)
16.Формулу вида <формула>::=<цифра>|(<формула><знак><формула>) <знак>::= + | - | * <цифра>::=0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 можно представить в виде бинарного дерева по следующим правилам: а)формула из одной цифры представляется деревом из одной вершины с этой цифрой; б)формула вида f1# f2 представляется деревом, в котором корень - это знак # соответствующей операции, а левое и правое поддеревья - это представления формул f1,f2 в виде бинарных деревьев. Например, формуле 5*(3+8) соответствует дерево
*
/ \
5 +
/ \
3 8
Требуется: а)построить дерево по формуле указанного выше вида; б)вычислить как целое число значение дерева-формулы; в)по заданному дереву распечатать соответствующую формулу.
17.Составить программу, формирующую “перекрестные списки”, т.е. печатающую список слов, которые встречаются в анализируемом файле, а для каждого слова - список номеров строк, в которых это слово встречается. При решении задачи рекомендуется использовать следующие структуры данных: struct LIST //список номеров строк для данного слова
{ int num; struct LIST* p; }
struct NODE //узел дерева с информацией об очередном слове
18.Распечатать слова текста, отсортированные в порядке убывания частоты их встречаемости (рядом с каждым словом выводить значение счетчика частоты его вхождений в текст). При решении задачи использовать дерево следующей структуры:
struct NODE
{char* word; int k; struct NODE* left; struct NODE* right;}.
19.В упорядоченном бинарном дереве удалить узел с указанным ключом.
20.В упорядоченном бинарном дереве удалить все листья.
21.Вывести упорядоченное бинарное дерево послойно, начиная с корня, сначала исходное, а затем – после удаления указанного слоя.
22.Составить программу, которая содержит текущую информацию о книгах в библиотеке. Сведения о книгах содержат: номер УДК, фамилию и инициалы автора, название, год издания, количество экземпляров данной книги в библиотеке. Программа должна обеспечивать: а)начальное формирование данных о всех книгах в библиотеке в виде бинарного дерева; б)добавление данных о книгах, вновь поступающих в библиотеку; в)удаление данных о списываемых книгах; г)по запросу выдаются сведения о наличии книг в библиотеке, упорядоченные по годам издания.
23.Составить программу, которая содержит текущую информацию о заявках на авиабилеты. Каждая заявка содержит: пункт назначения, номер рейса, фамилию и инициалы пассажира, желаемую дату вылета. Программа должна обеспечивать: а)хранение всех заявок в виде бинарного дерева; б)добавление и удаление заявок; в) по заданному номеру рейса и дате вылета вывод заявок с их последующим удалением; г)вывод всех заявок.
24.Англо-русский словарь построен как бинарное дерево. Каждая компонента содержит английское слово, соответствующее ему русское слово и счетчик количества обращений к данной компоненте. Первоначально дерево формируется согласно английскому алфавиту. В процессе эксплуатации словаря при каждом обращении к компоненте в счетчик обращений добавляется единица. Составить программу, которая: 1)обеспечивает начальный ввод словаря с конкретными значениями счетчиков обращений; 2)формирует новое представление словаря в виде бинарного дерева по следующему алгоритму: а)в старом словаре находится компонента с наибольшим значением счетчика обращений, б)найденная компонента заносится в новый словарь и удаляется из старого, в)переход к п. ‘а)’ до исчерпания исходного словаря; 3)производит вывод исходного и нового словарей. Программа должна обеспечивать диалог с помощью меню и контроль ошибок при вводе.
25.На междугородной телефонной станции картотека абонентов, содержащая сведения о телефонах и их владельцах, организована как бинарное дерево. Составить программу, которая: 1)обеспечивает начальное формирование картотеки в виде бинарного дерева; 2)производит вывод всей картотеки; 3)вводит номер телефона и время разговора; 4)выводит извещение на оплату телефонного разговора. Программа должна обеспечивать диалог с помощью меню и контроль ошибок при вводе.
26.Автоматизированная информационная система на железнодорожном вокзале содержит сведения об отправлении поездов дальнего следования. Для каждого поезда указывается: номер поезда, станция назначения, время отправления. Данные в информационной системе организованы в виде бинарного дерева. Составить программу, которая: 1)обеспечивает первоначальный ввод данных в информационную систему и формирование бинарного дерева; 2)производит вывод всего дерева; 3)вводит номер поезда и выводит все данные об этом поезде; 4)вводит название станции назначения и выводит данные обо всех поездах, следующих до этой станции. Программа должна обеспечивать диалог с помощью меню и контроль ошибок при вводе.
27.Бинарное упорядоченное дерево стереть слой за слоем, начиная с максимально дальней вершины.
28.В идеально сбалансированном бинарном дереве удалить узел с заданным ключом.
29.По исходному бинарному дереву построить подобное дерево.
30.По исходному бинарному дереву построить зеркально подобное дерево.