Вы находитесь на странице: 1из 16

Министерство цифрового развития, связи и массовых коммуникаций

Российской Федерации
Федеральное государственное образовательное бюджетное учреждение
высшего профессионального образования
«Санкт-Петербургский государственный университет телекоммуникаций
им. проф. М.А. Бонч-Бруевича»
Кафедра «Безопасности информационных систем»
Дисциплина «Алгоритмы и структуры данных»

ОТЧЁТ
по практической работе №6
«ХЕШ-ТАБЛИЦЫ»

Выполнили: студенты группы ИСТ-032

М. О. Алексеева, В. И. Эверстова

Принял: доцент кафедры, к.т.н.

«___» ________ 2021 г. _________/C.А. Медведев/

Санкт-Петербург
2021
Цель работы:
Получить практические навыки работы с хеш-таблицами. Научиться
применять основные алгоритмы работы с хеш-таблицами.

Задание:
Доработать код из примера так, чтобы вместо множества получился
ассоциативный массив, в котором можно не только проверить наличие
ключа, добавить или удалить ключ, но и связывать с ключом значение.
Написать программу, позволяющую работать с ассоциативным
массивом, основанным на хеш-таблице. Программа должна позволять
заполнять массив записями с ключами и значениями, удалять записи по
ключу, выводить всю информацию о записи по ключу, выводить всю
информацию, хранящуюся в ассоциативном массиве. Программа должна
выдавать предупреждение при попытке дважды добавить один и тот же
ключ.

Индивидуальное задание:
Для каждой даты хранится информация о погоде: количество
осадков выпавшее за день, минимальная и максимальная температура,
балл облачности (от 0 до 10). Требуется получать информацию о погоде в
указанную дату.
Ход решения

Функция поиска имеет вычислительную сложность: в среднем случае

O(1), когда распределение значений самой хеш-функции близко к

случайному, но когда хеш-функции всегда возвращает одно и то же

значение, тогда в худшем случае сложность составит O(n).

Вычислительная сложность функции вставки данных имеет

вычислительную сложность: чем больше хеш-таблица, тем реже будет

вызываться перехеширование, поэтому вычислительная сложность

добавления в среднем случае равна O(1), но поскольку иногда при вставке

все-таки может потребоваться операция перехеширования, тогда в худшем

случае сложность составит O(n).

Вычислительная сложность функции удаления составит строго O(1).

Функция перехеширования имеет вычислительную сложность: O(n) - так

как, содержит цикл, который содержит только элементарные операции и

выполняется n раз. К тому же необходимо учесть, что при

перехешировании вызывается функция get_prime, которая , в свою очередь

вызывает функцию is_prime. Функция is_prime проверяет число на

простоту и имеет вычислительную сложность O(√ n). Функция get_prime

производит поиск ближайшего простого числа не меньше указанного и

имеет вычислительную сложность O(log n).Однако мы знаем, что если


вычислительная сложность имеет вид суммы нескольких функций, то

всеми функциями, кроме самой быстро растущей, можно пренебречь.

Тогда вычислительная сложность перехеширования действительно

составляет O(n).

Функция вывода в среднем имеет вычислительную сложность O(n).


Вывод
Мы приобрели практические навыки работы с хеш-таблицами.
Научились применять основные алгоритмы работы с хеш-таблицами.
Листинг программы

#include <iostream>
#include <windows.h>
#include <fstream>
#include <vector>
#include <algorithm>
#include <conio.h>

using namespace std;

int n;
// Структура, являющаяся примером ключа хеш-таблицы
struct Key
{
char date[11]; // Строка не более чем из 19 символов (20-й
терминальный)
};

struct Data {
int osadki;
int maxt;
int mint;
int oblaka;
};

// Элемент хеш-таблицы со служебными данными


struct Node
{
Key m_key; // Ключ, добавленный в хеш-таблицу
Data d;
size_t m_hash; // Значение хеш-функции ключа
int m_next; // Индекс следующего узла в массиве элементов
};

struct Hashtable
{
int m_free_index; // Индекс последнего удалённого элемента
size_t m_last_index; // Индекс последнего добавленного элемента
с ключом
size_t m_count; // Логический размер хеш-таблицы (количество
ключей)
size_t m_capacity; // Физический размер хеш-таблицы (размер
массивов)
Node * m_nodes; // Массив с элементами, содержащими ключи
int * m_indexes; // Индексы элементов по значениям хеш-функции
};

// Инициализация хеш-таблицы
Hashtable init_hashtable()
{
Hashtable table;
table.m_free_index = -1;
table.m_last_index = 0;
table.m_count = 0;
table.m_capacity = 0;
table.m_nodes = nullptr;
table.m_indexes = nullptr;
return table;
}

bool is_prime(size_t n) // Функция проверки числа на простоту


{
if (n < 4)
return n > 1;
if (n % 2 == 0 || n % 3 == 0)
return false;
for (size_t k = 5; k * k <= n; k += 6)
{
if (n % k == 0)
return false;
if (n % (k + 2) == 0)
return false;
}
return true;
}

//Функция поиска ближайшего простого числа не меньше указанного.


size_t get_prime(size_t n)
{
if (n < 3)
return 3;
if (n % 2 == 0)
n++;
if (n % 3 == 0)
n += 2;
bool even = (n % 6) == 1;
while (!is_prime(n))
{
n += even ? 4 : 2;
even = !even;
}
return n;
}

// Увеличение размера массивов индексов и элементов,


// если они заполнены, с пересчётом индексов
void increase_capacity(Hashtable *table)
{
// Вместимость хеш-таблицы должна быть простым числом
size_t prime = get_prime(table->m_capacity * 2 + 1);
Node *tmp_nodes = new Node[prime];
int *tmp_indexes = new int[prime];
memset(tmp_nodes, 0, sizeof(Node) * prime);
memset(tmp_indexes, -1, sizeof(int) * prime);
// Копируем элементы хеш-таблицы в новый массив
if (table->m_nodes != nullptr)
{
memcpy(tmp_nodes, table->m_nodes,
sizeof(Node) * table->m_last_index);
}
// Пересчитываем индексы всех элементов хеш-таблицы
for (size_t i = 0; i < table->m_last_index; i++)
{
size_t index = tmp_nodes[i].m_hash % prime;
tmp_nodes[i].m_next = tmp_indexes[index];
tmp_indexes[index] = i;
}
// Очищаем память от старых данных
if (table->m_nodes != nullptr)
delete [] table->m_nodes;
if (table->m_indexes!= nullptr)
delete [] table->m_indexes;
// Заменяем старые указатели на массивы новыми
table->m_nodes = tmp_nodes;
table->m_indexes = tmp_indexes;
table->m_capacity = prime;
}

void add_node(Hashtable *table, const Key& key, size_t hashCode, int


_osadki, int _mint, int _maxt, int _oblaka)
{
int node;
size_t index;
// Если что-то было удалено из таблицы,
// вставить можно на освободившееся место
if (table->m_free_index >= 0)
{
node = table->m_free_index;
table->m_free_index = table->m_nodes[node].m_next;
}
else
{
// Если закончилось место, его надо увеличить
if (table->m_last_index == table->m_capacity)
increase_capacity(table);
// Вставляем новый элемент в конец
node = table->m_last_index;
table->m_last_index++;
}
index = hashCode % table->m_capacity;
// Проставляем все ключи и индексы в хеш-таблице
table->m_nodes[node].m_hash = hashCode;
table->m_nodes[node].m_key = key;
table->m_nodes[node].d.maxt = _maxt;
table->m_nodes[node].d.osadki = _osadki;
table->m_nodes[node].d.mint = _mint;
table->m_nodes[node].d.oblaka = _oblaka;
table->m_nodes[node].m_next = table->m_indexes[index];
table->m_indexes[index] = node;
table->m_count++;
}

size_t get_hash(const Key& key)


{
size_t result = numeric_limits<size_t>::max();
for (size_t i = 0; i < sizeof(key.date); i++)
{
result ^= key.date[i] <<
((i * 7) % ((sizeof(size_t) - sizeof(char)) * 8));
if (key.date[i] == '\0')
break;
}
return result;
}

bool operator==(const Key& first, const Key& second)


{
for (size_t i = 0; i < sizeof(first.date); i++)
{
if (first.date[i] == '\0' && second.date[i] == '\0')
return true;
if (first.date[i] != second.date[i])
return false;
}
return true;
}

inline int combine(int index, size_t * hash, size_t hashCode, int *


previous, int prev)
{
if (hash != nullptr)
*hash = hashCode;
if (previous != nullptr)
*previous = prev;
return index;
}

int find_node(const Hashtable* table, const Key &key, size_t *hash,


int *previous)
{
int prev = -1;
size_t hash_code = get_hash(key);

if (hash_code == 0) // Нулевое значение хеш-функции


зарезервировано
hash_code++;

// В пустой хеш-таблице не может быть никакого ключа


if (table->m_capacity == 0)
return combine(-1, hash, hash_code, previous, prev);

// Остаток от деления значения хеш-функции на размер хеш-таблицы


int index = table->m_indexes[hash_code % table->m_capacity];
// Перебор всех элементов, которые будут находиться по этому
индексу
while (index >= 0)
{
if (table->m_nodes[index].m_hash == hash_code && key ==
table->m_nodes[index].m_key) // Эквивалентность!
{
// Такой ключ в хеш-таблице есть
return combine(index, hash, hash_code, previous, prev);
}
prev = index; // Предыдущий элемент понадобится для удаления
index = table->m_nodes[index].m_next;
}
// Такого ключа в хеш-таблице нет
return combine(-1, hash, hash_code, previous, prev);
}
void enumerate(const Hashtable *table)
{
if (table->m_nodes == nullptr)
return;
for (size_t i = 0; i < table->m_last_index; i++)
{
// Вот из-за этого хеш-код не должен быть равен 0
if (table->m_nodes[i].m_hash != 0)
cout << table->m_nodes[i].m_key.date << " " << table-
>m_nodes[i].d.osadki << " " << table->m_nodes[i].d.mint << " " <<
table->m_nodes[i].d.maxt << " " << table->m_nodes[i].d.oblaka <<
endl;
}
}

void remove_node(Hashtable *table, size_t hashCode, int node, int


previous)
{
int index = table->m_indexes[hashCode % table->m_capacity];

// В зависимости от того, является ли удаляемый элемент первым в


списке
// элементов с этим индексом, по-разному удаляем его из цепочки
элементов
if (previous < 0)
table->m_indexes[index] = table->m_nodes[node].m_next;
else
table->m_nodes[previous].m_next = table-
>m_nodes[node].m_next;

// Обнуляем элемент
table->m_nodes[node].m_hash = 0;
table->m_nodes[node].m_next = table->m_free_index;
table->m_count--;

// Обнуляем ключ, хранящийся в таблице


memset(&table->m_nodes[node].m_key, 0, sizeof(Key));
if (table->m_count == 0)
{
table->m_last_index = 0;
table->m_free_index = -1;
}
else // Оставляем индекс удалённого элемента для будущих
добавлений
table->m_free_index = node;
}

// Добавление ключа в хеш-таблицу


bool add(Hashtable *table, const Key& key, int _osadki, int _mint,
int _maxt, int _oblaka)
{
size_t hashCode;
// Если ключ уже есть в хеш-таблице, повторно его не добавляем
if (find_node(table, key, &hashCode, nullptr) >= 0)
return false;

add_node(table, key, hashCode, _osadki, _mint, _maxt, _oblaka);


return true; // Ключ успешно добавлен
}
// Удаление ключа из хеш-таблицы.
// Если ключ не найден, функция возвращает false
bool remove1(Hashtable *table, const Key& key)
{
size_t hash_code; int previous;
int node = find_node(table, key, &hash_code, &previous);
if (node < 0) // Не нашли ключ в таблице
return false;
remove_node(table, hash_code, node, previous);
return true; // Нашли и удалили
}

// Проверка наличия ключа в хеш-таблице


bool contains(const Hashtable *table, const Key& key)
{
return find_node(table, key, nullptr, nullptr) >= 0;
}

void print_search(const Hashtable* table, const Key &key)


{
size_t hash_code = get_hash(key);
int index = table->m_indexes[hash_code % table->m_capacity];

while (index >= 0)


{
if (table->m_nodes[index].m_hash == hash_code && key ==
table->m_nodes[index].m_key)
{
cout << table->m_nodes[index].m_key.date << " "<<
table->m_nodes[index].d.osadki << " " << table-
>m_nodes[index].d.mint << " " << table->m_nodes[index].d.maxt << "
" << table->m_nodes[index].d.oblaka<< endl;
}
index = table->m_nodes[index].m_next;
}

void Menu()
{

cout << "Выберите пункт меню:" << endl;


cout << "0 - Выход" << endl;
cout << "1 - Вывести данные" << endl;
cout << "2 - Поиск записи по ключу" << endl;
cout << "3 - Добавление узла" << endl;
cout << "4 - Удалениие узла" << endl;
cout << "5 - Изменение узла" << endl;

cin >> n;
}

int main()
{
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
ifstream tmp("Pogoda.txt");
Hashtable table;
table = init_hashtable();
Key key;
Data data;

ifstream in("Pogoda.txt");
if (in.is_open())
{

while (!in.eof())
{
in >> key.date >> data.osadki >> data.mint >>
data.maxt >> data.oblaka;
add(&table, key, data.osadki, data.mint, data.maxt,
data.oblaka);
}
}
Menu();
while(n!=0)
{
switch (n)
{
case 1:
{
enumerate(&table);
cout<<endl;
Menu();
break;
}
case 2:
{
char c[11];
cout << "Введите ключ: ";
cin.ignore();
cin.getline(c,sizeof(c));
for(int i =0; i<sizeof(c); i++)
{
key.date[i] = c[i];
}
size_t hash_code = get_hash(key);
int previous;
if (find_node(&table, key, nullptr, nullptr)==-1)
cout<<"Ключ не найден!"<<endl;
else
{
cout<<"Ключ найден!"<<endl;
print_search (&table, key);
}

Menu();
break;
}
case 3:
{
cout << "Введите дату (ключ), количество осадков,
минимальную температуру, максимальную температуру, балл
облачности(0-10): "<<endl;
cin >> key.date >> data.osadki >> data.mint >> data.maxt
>> data.oblaka;
add(&table, key,data.osadki, data.mint, data.maxt,
data.oblaka);
cout<<endl;
Menu();
break;
}
case 4:
{
char c[11];
cout << "Введите ключ: ";
cin.ignore();
cin.getline(c,sizeof(c));
for(int i =0; i<sizeof(c); i++)
{
key.date[i] = c[i];
}
size_t hash_code;
int previous;
cout<<"Удалена строка: ";
print_search (&table, key);
remove1(&table, key);
cout<<endl;
Menu();
break;
}

case 5: //Изменение информации


{
int choice;
cout << "Что Вы хотети изменить? " << endl
<< "1. Количество осадков" << endl
<< "2. Минимальная температура" << endl
<< "3. Максимальная температура" << endl
<< "4. Балл облачности(0-10)" << endl;
cin >> choice;

cout << "Введите дату (ключ): ";


cin.ignore();
char c[20];
cin.getline(c,sizeof(c));
for(int i =0; i<sizeof(c); i++)
{
key.date[i] = c[i];
}

size_t hash_code;
int previous;

size_t node = contains(&table, key);


if(node==-1){
cout<<"Неправильный ключ :("<<endl;
Menu();
break;
}

switch (choice)
{
case 1:
{
cout << node;
cout << "Введите количество осадков: ";
int _osadki;
cin >> _osadki;
table.m_nodes[node].d.osadki = _osadki;
Menu();
break;
}
case 2:
{
cout << "Введите минимальную температуру: ";
int _mint;
cin >> _mint;
table.m_nodes[node].d.mint=_mint;
Menu();
break;
}
case 3:
{
cout << "Введите максимальную температуру: ";
int _maxt;
cin >> _maxt;
table.m_nodes[node].d.maxt=_maxt;
Menu();
break;
}
case 4:
{
cout << "Введите балл облачности(0-10): ";
int _oblaka;
cin >> _oblaka;
table.m_nodes[node].d.oblaka=_oblaka;
Menu();
break;
}
break;
}
break;
}
default:

Menu();
}
in.close();
}

Вам также может понравиться