Варианты амбициозного уровня
Вариант №16:
Реализуйте структуру-множество целых чисел на основе 2-3 дерева. Для упрощения задачи разрешается опустить операцию удаления узлов. 2-3 дерево – дерево с двумя типами узлов:
листья, хранящие конкретные значения;
внутренние узлы, содержащие 3 связи с дочерними узлами и минимальные значения, хранящиеся во втором и третьем поддереве.
Внутренние узлы могут содержать как 2, так и 3 поддерева. Если остается только 1 поддерево, узел уничтожается и заменяется поддеревом. Если требуется 4 поддерева, узел разрывается пополам и вводится еще один уровень глубины.
Проведите измерения производительности операции вставки и максимальной высоты в создаваемом дереве на достаточно больших множествах для:
последовательности элементов в случайном порядке;
последовательности элементов, отсортированной по возрастанию;
последовательности элементов, отсортированной по убыванию.
Подробнее о данной структуре можно прочесть по следующей ссылке:
Р. Седжвик, глава 13 “Сбалансированные деревья”, раздел 13.3 “Нисходящие 2-3-4 деревья”
Вариант №17:
Реализуйте структуру и основные операции автоматически балансирующегося BST на основе алгоритма, предполагающего рандомизированное вращение узлов дерева при вставке. Проведите измерения производительности операции вставки и максимальной высоты в создаваемом дереве на достаточно больших множествах для:
последовательности элементов в случайном порядке;
последовательности элементов, отсортированной по возрастанию;
последовательности элементов, отсортированной по убыванию.
Подробнее о данной структуре можно прочитать по следующей ссылке:
Р. Седжвик, глава 13 “Сбалансированные деревья”, раздел 13.1 “Рандомизированные BST-деревья”.
Вариант №18:
Разреженная матрица представляет собой матрицу со специальным способом хранения, ориентированным на экономию памяти. Ключевая идея разреженной матрицы состоит в том, чтобы хранить значения только тех коэффициентов, которые отличны от 0. Хранение можно реализовать при помощи хэш-таблицы, у которой в качестве ключа выступает пара индексов матрицы (номер столбца и номер строки), а в качестве значения – хранимый коэффициент. В качестве хэш-функции подойдет функция, складывающая номер строки с номером столбца, умноженным на количество строк. Необходимо реализовать следующие операции и проверить их работу при помощи достаточно полной тестовой программы:
Функцию создания матрицы, принимающую число строк и чисто столбцов. Изначально матрица должна предполагать, что все хранимые коэффициенты равны 0.
Функцию уничтожения матрицы.
Функции доступа и модификации конкретной ячейки матрицы по индексам.
Функцию печати матрицы в поток – на стандартный поток вывода по умолчанию.
Функции сложения, вычитания и умножения матриц.
Функцию транспонирования матрицы.
Контрольные вопросы
Понятие хэш-функции. Основные требования к хэш-функциям. Понятие коллизии.
Разрешение коллизий в хэш-таблицах в случае открытого хэширования.
Разрешение коллизий в хэш-таблицах в случае закрытого хэширования.
АТД “Дерево”. Виды узлов в дереве. Высота дерева. Полное и неполное дерево. Арность дерева. Обход деревьев.
АТД “Дерево”: описание реализации на основе массива индексов родительских узлов.
АТД “Дерево”: описание реализации на основе списков дочерних узлов.
АТД “Дерево”: описание реализации на основе динамической структуры узлов.
Бинарное дерево поиска. Характеристическое свойство. Вычислительная сложность всех операций. Сбалансированное и несбалансированное дерево.
Операция вращения узлов в BST. Свойства, принцип реализации.
Основные принципы организации красно-черных BST-деревьев. Дополнительные свойства-ограничения. Примеры корректирующих действий.
Пример решения задачи базового уровня
Задача:Пользователь вводит в программу последовательность целых чисел, завершая ввод нажатием <Ctrl+Z>. Числа могут повторяться. Программа собирает вводимые данные в память, а в конце ввода в произвольном порядке выдает набор из пар чисел на отдельных строках - первое из которых является одним из введенных чисел без повторений, а второе - количество его появлений во вводе. Например, пользователь вводит такую последовательность :
1 2 3 4 5 1 3 5 6 1 <Ctrl+Z>
На что программа выдает следующий результат обработки (строки могут быть в любом порядке):
1 3
2 1
3 2
4 1
5 2
6 1
Реализация на основе хэш-таблиц:
Для реализации задачи требуется реализовать функцию обхода хранящихся ячеек в хэш-таблице. В заголовочный файл с хэш-таблицей (hash_table.hpp) следует добавить такое объявление для функции обхода, которая будет отправлять пару ключ-значение функции обратного вызова:
typedef void ( * HashTableWalkFunction ) ( int _key, int _value );
void HashTableWalk ( const HashTable & _ht, HashTableWalkFunction _f );
Можно реализовать такую функцию для таблицы с закрытым хэшированием. Для этого нужно в файл реализации hash_table_closed_impl.cpp добавить тело функции обхода:
void HashTableWalk ( const HashTable & _ht, HashTableWalkFunction _f )
{
// Находим ячейки, имеющие статус OCCUPIED, и вызываем функцию пользователя
for ( int i = 0; i < _ht.m_tableSize; i++ )
if ( _ht.m_pData[ i ].m_status == HashTable::Element::OCCUPIED )
( * _f )( _ht.m_pData[ i ].m_key, _ht.m_pData[ i ].m_value );
}
В качестве альтернативы, такую же функции можно добавить в таблицу с открытым хэшированием. Для этого файл реализации hash_table_open_impl.cpp следует расширить:
void HashTableWalk ( const HashTable & _ht, HashTableWalkFunction _f )
{
// Находим ячейки, имеющие списки
for ( int i = 0; i < _ht.m_tableSize; i++ )
if ( _ht.m_pData[ i ] )
{
// Обходим все элементы списка
const HashTable::Element * element = _ht.m_pData[ i ];
while ( element )
{
( * _f )( element->m_key, element->m_value );
element = element->m_pNext;
}
}
}
Наконец, составим основную часть программы в соответствии с условием:
#include "hash_table.hpp"
#include <iostream>
// Функция реакции на обход таблицы - печатает пару ключ-значение
void printKeyValue ( int _key, int _value )
{
std::cout << _key << ' ' << _value << std::endl;
}
// Основная программа
int main ()
{
// Создаем хэш-таблицу
HashTable * pHT = HashTableCreate( 10 );
// Цикл ввода данных
while ( true )
{
// Вводим очередное число
int value;
std::cin >> value;
// Прекращаем ввод при нажатии Ctrl+Z или в случае ошибки
if ( std::cin )
{
// Был ли данный ключ уже размещен в таблице ранее?
int count = HashTableGet( * pHT, value );
if ( count == -1 )
// Первое вхождение ключа
HashTableInsert( * pHT, value, 1 );
else
// Очередное вхождение ранее наблюдавшегося ключа
HashTableInsert( * pHT, value, count + 1 );
}
else
break;
}
// Инициируем обход таблицы с распечаткой пар ключ-значение
HashTableWalk( * pHT, & printKeyValue );
// Уничтожаем таблицу
HashTableDestroy( pHT );
}
Результат выполнения программы:

