9. Деревья 9.1. Реализовать абстрактный класс Tree – базовый класс, описывающий общий интерфейс для двоичных деревьев поиска. Объявите структуру Node (узел дерева) в разделе public класса Tree. Узел должен содержать поле данных типа int, указатели на левое и правое поддеревья, указатель на родителя и высоту (типа int) поддерева с корнем в данном узле. Для класса Tree объявите следующие чистые виртуальные функции: a) void insert(int x) – метод добавления нового элемента в дерево; b) void remove(Node *p) – метод, удаляющий из дерева узел по указателю p; Реализуйте следующие методы: c) int get_height(Node *p) – метод, возвращающий высоту поддерева с корнем в узле p. Если p == NULL, то возвращается -1. d) node* find(int x) – метод поиска элемента по значению х. Если элемент найден, то возвращается указатель на узел, содержащий данный элемент, в противном случае возвращается NULL. e) методы нахождения наибольшего и наименьшего элементов: Node* minimum(Node *p) – метод, возвращающий указатель на наименьший элемент поддерева с корнем в узле p; Node* maximum(Node *p) – метод, возвращающий указатель на наибольший элемент поддерева с корнем в узле p; f) методы нахождения предшествующего и последующего элементов: Node* predecessor(const Node *p) – метод, возвращающий указатель на узел, предшествующий по ЗНАЧЕНИЮ узлу p. Если такого узла не найдено (то есть p хранит наименьший элемент), вернуть NULL; Node* successor(const Node *p) – метод, возвращающий указатель на узел, следующий по ЗНАЧЕНИЮ за узлом p. Если такого узла не найдено (то есть p хранит наибольший элемент), вернуть NULL; g) три варианта обхода дерева в глубину (методы должны выводить на экран элементы дерева в порядке, соответствующем обходу): void in_order_traversal(); void pre_order_traversal(); void post_order_traversal(); h) void breadth_first_traversal() – обход дерева в ширину (при реализации можно использовать контейнер queue из STL); i) (необязательный пункт) перегрузить оператор вывода в предположении, что в дереве хранятся только однозначные или двузначные натуральные числа. Отображать дерево следует в любой удобной Вам форме, этот оператор понадобится Вам при реализации сбалансированного дерева. Например, вывод дерева на экран может выглядеть следующим образом: От класса Tree унаследуйте класс BSTree (binary search tree) – двоичное дерево поиска. В данном классе реализуйте виртуальные методы insert и remove. При этом при выполнении этих методов высоты всех поддеревьев должны автоматически пересчитываться, но вычислительная сложность методов должна оставаться равной O(h), где h – это высота всего дерева. Описания интерфейсов классов должны содержаться в отдельных h-файлах (tree.h, bs_tree.h), а вся реализация – в cpp-файлах (tree.cpp, bs_tree.cpp). Не забудьте про конструкторы и деструкторы. Не забудьте про правило трех: запретите использование конструкторов копирования и операторов присваивания или перегрузите их. В функции main создайте динамический объект класса BSTree через указатель Tree* p = new BSTree, заполните дерево случайными натуральными числами, не превышающими 100. Вызовите все методы обхода дерева. Используя метод successor, выведите на экран только четные элементы дерева в порядке возрастания, с помощью метода predecessor выведите на экран только нечетные элементы в порядке убывания. Удалите из дерева все элементы, кратные трем. Выведите дерево с помощью любого метода обхода. Удалите динамический объект. 9.2. Добавьте в проект ещё два файла и реализуйте класс AVLTree (АВЛ-дерево), хранящее целые числа типа int. Для этого достаточно унаследовать класс AVLTree от класса Tree и переопределить только два метода insert и remove. Для удобства повороты и балансировку дерева выделите в отдельные методы: int balance_factor(const Node *p) – метод, вычисляющий коэффициент сбалансированности (balance factor) узла p; void reset_height(Node *p) – метод, пересчитывающий высоту поддерева с узлом в корне p; void rotate_right(Node *p) – метод, осуществляющий правый поворот вокруг узла p; void rotate_left(Node *p) – метод, осуществляющий левый поворот вокруг узла p; void balance(Node *p) – метод, осуществляющий балансировку поддерева с корнем в узле p; не забудьте про деструктор и правило трех. В некотором файле tree.in описаны действия, совершаемые над двоичным деревом поиска, в файле tree.out выведите результат выполнения описанных действий. Формат входного файла В первой строке файла tree.in записано целое число (0 или 1), 0 означает, что будет рассматриваться обычное двоичное дерево поиска, 1 – АВЛ-дерево. Далее в каждой строке описывается действие и соответствующие параметры: i x – добавить элемент x (0 < x < 100) в дерево; d x – удалить элемент x (0 < x < 100) из дерева; ino – вывести в выходной файл все элементы дерева в соответствии с in-order обходом; preo – вывести в выходной файл все элементы дерева в соответствии с pre-order обходом; posto – вывести в выходной файл все элементы дерева в соответствии с post-order обходом; bfs – вывести в выходной файл все элементы дерева в соответствии с обходом в ширину; h – вывести в выходной файл высоту дерева. Формат выходного файла В файле tree.out должен содержаться результат выполнения команд, описанных в файле tree.in: каждый результат должен быть выведен в отдельной строке. Примеры tree.in 0 i 20 i 4 i 15 preo h 1 i 20 i 4 i 15 preo h 1 i 1 i 2 i 3 d 2 d 1 d 3 h i 4 h 1 i 5 i 2 i 8 i 1 i 3 i 7 i 10 i 4 i 6 i 9 i 11 i 12 bfs h d 1 bfs h tree.out 20 4 15 2 15 4 20 1 -1 0 5 2 8 1 3 7 10 4 6 9 11 12 4 8 5 10 3 7 9 11 2 4 6 12 3 Хеш-таблицы 9.3. В файле tale.in записан текст на некотором языке, использующем латинский алфавит. Известно, что длина каждого слова не превосходит восьми символов. Слова разделены пробелами, точками, запятыми и переносами строки. Вам необходимо сосчитать частоту вхождения каждого слова в данный текст. Для этого следует использовать хеш-таблицу, ключом для которой будет служить строка символов. Максимальная длина строки – восемь символов, если строка короче, то остальные символы считаются равными ‘\0’. Пусть строка хранится в массиве char str[8], тогда значение хеш-функции будет вычисляться по формуле: , где m – размер таблицы, любое простое число больше 127, а значения принадлежат множеству {0, 1, 2, … m-1}. Выберите самостоятельно некоторое значение m, числа можно также придумать самостоятельно или определять их случайным образом до начала построения таблицы. Для хеш-таблицы должен быть определен метод add(char *str), который будет добавлять строку str в таблицу и устанавливать её счетчик «встречаемости» равным единицы, если же данная строка уже находится в таблице, то её счетчик должен увеличиться на единицу. После обработки всего текста выведите на экран в любом порядке слова и соответствующие им частоты. Примечание Используйте метод цепочек (списков) для разрешения коллизий в хеш-таблице. Пример tale.in вывод на экран quod licet iovi, non licet bovi quod - 1 licet - 2 iovi - 1 non - 1 bovi - 1 Ассоциативные контейнеры set и map стандартной библиотеки 9.4. Напишите шаблонную функцию пересечения двух множеств, типы элементов которых совпадают. Напишите шаблонную функцию объединения двух множеств, типы элементов которых совпадают. Напишите шаблонную функцию, принимающую множество множеств и возвращающую значение «истина», если оно является замкнутым относительно объединения и пересечения. Реализуйте класс point, описывающий точку на плоскости. Перегрузите для данного класса операторы ввода/вывода и оператор сравнения < (будем считать, что (x1, y1) < (x2, y2), если либо x1 < x2, либо x1 = x2 и y1 < y2). Пользователь вводит с экрана пять точек (то есть пять пар чисел (xi, yi)). Пусть данные точки образуют множество A. Найдите множество B всех подмножеств множества А, выведите множество В на экран. Определите, будет ли множество В замкнутым относительно объединения и пересечения. 9.5. В файле dict.txt содержится описание шифра: в каждой строке файла записано целое число n (1 ≤ n ≤ 32000), пробел и слово, соответствующее этому числу. Считайте данный файл и постройте на его основе отображение map. Пользователь вводит зашифрованный текст (целые числа, разделенные пробелами, признак конца ввода – число ноль). Расшифруйте послание пользователя, используя построенное отображение. Если какое-то число отсутствует в шифре, то при расшифровке его надо проигнорировать. Файл dict.txt 569 a 570 the 17 know 321 treasure 3 secret 123 I Пример входных данных 123 17 569 47 3 0 Пример выходных данных I know a secret