Цель лекции: изучить построение функции
Процесс поиска данных в больших объемах информации сопряжен с временными затратами, которые обусловлены необходимостью просмотра и сравнения с ключом поиска значительного числа элементов. Сокращение поиска возможно осуществить путем
В настоящее время используется широко распространенный метод обеспечения быстрого доступа к информации, хранящейся во внешней памяти –
Хеширование (или хэширование, англ. hashing ) – это преобразование входного массива данных определенного типа и произвольной длины в выходную битовую строку фиксированной длины. Такие преобразования также называются хеш-функциями или функциями
Хеш-таблица – это структура данных, реализующая интерфейс ассоциативного массива, то есть она позволяет хранить пары вида "ключ- значение" и выполнять три операции: операцию добавления новой пары, операцию поиска и операцию удаления пары по ключу. Хеш-таблица является массивом, формируемым в определенном порядке
Принято считать, что хорошей, с точки зрения практического применения, является такая
При этом первое свойство хорошей
Если бы все данные были случайными, то
При возникновении
Хеш-таблицы должны соответствовать следующим свойствам.
O(1). Однако при такой оценке не учитываются возможные аппаратные затраты на перестройку индекса хеш-таблицы, связанную с увеличением значения размера массива и добавлением в хеш-таблицу новой пары.Метод цепочек. Технология сцепления элементов состоит в том, что
(рис 38.1) Разрешение коллизий при помощи цепочекКаждая ячейка массива является указателем на связный список (цепочку) пар ключ-значение, соответствующих одному и тому же хеш-значению ключа.
Операции поиска или удаления данных требуют просмотра всех элементов соответствующей ему цепочки, чтобы найти в ней элемент с заданным ключом. Для добавления данных нужно добавить элемент в конец или начало соответствующего списка, и, в случае если коэффициент заполнения станет слишком велик, увеличить размер массива и перестроить таблицу.
При предположении, что каждый элемент может попасть в любую позицию таблицы с равной вероятностью и независимо от того, куда попал любой другой элемент, среднее время работы операции поиска элемента составляет O(1+k), где k – коэффициент заполнения таблицы.
Метод открытой адресации. В отличие от NULL.
В этом случае, если ячейка с вычисленным индексом занята, то можно просто просматривать следующие записи таблицы по порядку до тех пор, пока не будет найден ключ разрешение
(рис 38.2) Разрешение коллизий при помощи открытой адресацииПри любом методе разрешения
Для успешной работы алгоритмов поиска, последовательность проб должна быть такой, чтобы все ячейки хеш-таблицы оказались просмотренными ровно по одному разу.
Удаление элементов в такой схеме несколько затруднено. Обычно поступают так: заводят логический флаг для каждой ячейки, помечающий, удален ли элемент в ней или нет. Тогда удаление элемента состоит в установке этого флага для соответствующей ячейки хеш-таблицы, но при этом необходимо модифицировать процедуру поиска существующего элемента так, чтобы она считала удаленные ячейки занятыми, а процедуру добавления – чтобы она их считала свободными и сбрасывала значение флага при добавлении.
Существует несколько типов функций
Простейшей организацией таблицы, обеспечивающей идеально быстрый поиск, является таблица прямого доступа. В такой таблице ключ является адресом записи в таблице или может быть преобразован в адрес, причем таким образом, что никакие два разных ключа не преобразуются в один и тот же адрес. При
Назовем пространством ключей множество всех теоретически возможных значений ключей записи. Назовем пространством записей множество тех ячеек памяти, которые выделяются для хранения таблицы. Таблицы прямого доступа применимы только для таких задач, в которых размер пространства записей может быть равен размеру пространства ключей. В большинстве реальных задач размер пространства записей много меньше, чем пространства ключей. Так, если в качестве ключа используется фамилия, то, даже ограничив
В целях экономии памяти можно назначать размер пространства записей равным размеру фактического множества записей или превосходящим его незначительно. В этом случае необходимо иметь некоторую функцию, обеспечивающую отображение точки из пространства ключей в точку в пространстве записей, то есть, преобразование ключа в адрес записи: a=h(k), где a – адрес, k – ключ.
Идеальной
Простейшей HashTableSize. Результат интерпретируется как адрес записи. Следует иметь в виду, что такая функция хорошо соответствует первому, но плохо – последним трем требованиям к
Если ключей меньше, чем элементов массива, то в качестве Key на HashTableSize, то есть:
Key % HashTableSize
Данная функция очень проста, хотя и не относится к хорошим. Вообще, можно использовать любую HashTableSize.
На практике, метод деления – самый распространенный.
//функция создания хеш-таблицы метод деления по модулю
int Hash(int Key, int HashTableSize) {
//HashTableSize
return Key % HashTableSize;
}
Следующей
Еще одной
В качестве P, интерпретируется как число в Q>P. Обычно выбирают Q=P+1. Это число переводится из системы Q обратно в систему P, приводится к размеру пространства записей и интерпретируется как адрес.
Основная идея базовой структуры при открытом (внешнем) В классов, пронумерованных от 0 до В-1, строится h(x) такая, что для любого элемента х исходного множества функция h(x) принимает целочисленное значение из интервала 0,1,...,В-1, соответствующее классу, которому принадлежит элемент х. Часто классы называют сегментами, поэтому будем говорить, что элемент х принадлежит сегменту h(x). Массив, называемый таблицей сегментов и проиндексированный номерами сегментов 0,1,...,В-1, содержит заголовки для B списков. Элемент х, относящийся к i -му списку – это элемент исходного множества, для которого h(x)=i.
Если сегменты примерно одинаковы по размеру, то в этом случае списки всех сегментов должны быть наиболее короткими при данном числе сегментов. Если исходное множество состоит из N элементов, тогда средняя длина списков будет N/B элементов. Если можно оценить величину N и выбрать В как можно ближе к этой величине, то в каждом списке будет один или два элемента. Тогда время выполнения операторов словарей будет малой постоянной величиной, не зависящей от N.
Пример 1.
#include "stdafx.h"
#include <iostream>
#include <fstream>
using namespace std;
typedef int T; // тип элементов
typedef int hashTableIndex; // индекс в хеш-таблице
#define compEQ(a,b) (a == b)
typedef struct Node_ {
T data;// данные, хранящиеся в вершине
struct Node_ *next; // следующая вершина
} Node;
Node **hashTable;
int hashTableSize;
hashTableIndex myhash(T data);
Node *insertNode(T data);
void deleteNode(T data);
Node *findNode (T data);
int _tmain(int argc, _TCHAR* argv[]){
int i, *a, maxnum;
cout << "Введите количество элементов maxnum : ";
cin >> maxnum;
cout << "Введите размер хеш-таблицы HashTableSize : ";
cin >> hashTableSize;
a = new int[maxnum];
hashTable = new Node*[hashTableSize];
for (i = 0; i < hashTableSize; i++)
hashTable[i] = NULL;
// генерация массива
for (i = 0; i < maxnum; i++)
a[i] = rand();
// заполнение хеш-таблицы элементами массива
for (i = 0; i < maxnum; i++) {
insertNode(a[i]);
}
// поиск элементов массива по хеш-таблице
for (i = maxnum-1; i >= 0; i--) {
findNode(a[i]);
}
// вывод элементов массива в файл List.txt
ofstream out("List.txt");
for (i = 0; i < maxnum; i++){
out << a[i];
if ( i < maxnum - 1 ) out << "\t";
}
out.close();
// сохранение хеш-таблицы в файл HashTable.txt
out.open("HashTable.txt");
for (i = 0; i < hashTableSize; i++){
out << i << " : ";
Node *Temp = hashTable[i];
while ( Temp ){
out << Temp->data << " -> ";
Temp = Temp->next;
}
out << endl;
}
out.close();
// очистка хеш-таблицы
for (i = maxnum-1; i >= 0; i--) {
deleteNode(a[i]);
}
system("pause");
return 0;
}
// хеш-функция размещения вершины
hashTableIndex myhash(T data) {
return (data % hashTableSize);
}
// функция поиска местоположения и вставки вершины в таблицу
Node *insertNode(T data) {
Node *p, *p0;
hashTableIndex bucket;
// вставка вершины в начало списка
bucket = myhash(data);
if ((p = new Node) == 0) {
fprintf (stderr, "Нехватка памяти (insertNode)\n");
exit(1);
}
p0 = hashTable[bucket];
hashTable[bucket] = p;
p->next = p0;
p->data = data;
return p;
}
//функция удаления вершины из таблицы
void deleteNode(T data) {
Node *p0, *p;
hashTableIndex bucket;
p0 = 0;
bucket = myhash(data);
p = hashTable[bucket];
while (p !compEQ(p->data, data)) {
p0 = p;
p = p->next;
}
if (!p) return;
if (p0)
p0->next = p->next;
else
hashTable[bucket] = p->next;
free (p);
}
// функция поиска вершины со значением data
Node *findNode (T data) {
Node *p;
p = hashTable[myhash(data)];
while (p !compEQ(p->data, data))
p = p->next;
return p;
}
При закрытом (внутреннем) х в сегмент с номером h(х), который уже занят другим элементом (коллизия), то в соответствии с методикой повторного h1(х),h2(х),..., куда можно поместить элемент х. Каждое из этих местоположений последовательно проверяется, пока не будет найдено свободное. Если свободных сегментов нет, то, следовательно, таблица заполнена, и элемент х добавить нельзя.
При поиске элемента х необходимо просмотреть все местоположения h(x),h1(х),h2(х),..., пока не будет найден х или пока не встретится пустой сегмент. Чтобы объяснить, почему можно остановить поиск при достижении пустого сегмента, предположим, что в хеш-таблице не допускается удаление элементов. Пусть h3(х) – первый пустой сегмент. В такой ситуации невозможно нахождение элемента х в сегментах h4(х),h5(х) и далее, так как при вставке элемент х вставляется в первый пустой сегмент, следовательно, он находится где-то до сегмента h3(х). Но если в хеш-таблице допускается удаление элементов, то при достижении пустого сегмента, не найдя элемента х, нельзя быть уверенным в том, что его вообще нет в таблице, так как сегмент может стать пустым уже после вставки элемента х.
Поэтому, чтобы увеличить эффективность данной реализации, необходимо в сегмент, который освободился после . В качестве альтернативы специальной константе можно использовать дополнительное поле таблицы, которое показывает состояние элемента. Важно различать константы и NULL – последняя находится в сегментах, которые никогда не содержали элементов. При таком подходе выполнение поиска элемента не требует просмотра всей хеш-таблицы. Кроме того, при вставке элементов сегменты, помеченные константой , можно трактовать как свободные, таким образом, пространство, освобожденное после удаления элементов, можно рано или поздно использовать повторно. Но если невозможно непосредственно сразу после удаления элементов пометить освободившиеся сегменты, то следует предпочесть закрытому
Существует несколько методов повторного h(x),h1(х),h2(х),...:
Линейное опробование сводится к
адрес=h(x)+ci,
где i – номер попытки разрешить коллизию;
c – константа, определяющая шаг перебора.
При шаге, равном единице, происходит последовательный перебор всех сегментов после текущего. Квадратичное опробование отличается от линейного тем, что шаг перебора сегментов нелинейно зависит от номера попытки найти свободный сегмент:
адрес=h(x)+ci+di2,
где i – номер попытки разрешить коллизию,
c и d – константы.
Благодаря нелинейности такой адресации уменьшается число проб при большом числе ключей-синонимов. Однако даже относительно небольшое число проб может быстро привести к выходу за адресное пространство небольшой таблицы вследствие квадратичной зависимости адреса от номера попытки.
Еще одна разновидность метода открытой адресации, которая называется двойным хешированием, основана на нелинейной адресации, достигаемой за счет суммирования значений основной и дополнительной хеш-функций:
адрес=h(x)+ih2(x).
Очевидно, что по мере заполнения хеш-таблицы будут происходить
Однако в случае многократного превышения
В случае применения схемы закрытого
Как только несколько последовательных сегментов будут заполнены, образуя группу, любой новый элемент при попытке вставки в эти сегменты будет вставлен в конец этой группы, увеличивая тем самым длину группы последовательно заполненных сегментов. Другими словами, для поиска пустого сегмента в случае непрерывного расположения заполненных сегментов необходимо просмотреть больше сегментов, чем при случайном распределении заполненных сегментов. Отсюда также следует очевидный вывод, что при непрерывном расположении заполненных сегментов увеличивается время выполнения вставки нового элемента и других операций.
Пример 2.
#include "stdafx.h"
#include <iostream>
#include <fstream>
using namespace std;
typedef int T; // тип элементов
typedef int hashTableIndex;// индекс в хеш-таблице
int hashTableSize;
T *hashTable;
bool *used;
hashTableIndex myhash(T data);
void insertData(T data);
void deleteData(T data);
bool findData (T data);
int dist (hashTableIndex a,hashTableIndex b);
int _tmain(int argc, _TCHAR* argv[]){
int i, *a, maxnum;
cout << "Введите количество элементов maxnum : ";
cin >> maxnum;
cout << "Введите размер хеш-таблицы hashTableSize : ";
cin >> hashTableSize;
a = new int[maxnum];
hashTable = new T[hashTableSize];
used = new bool[hashTableSize];
for (i = 0; i < hashTableSize; i++){
hashTable[i] = 0;
used[i] = false;
}
// генерация массива
for (i = 0; i < maxnum; i++)
a[i] = rand();
// заполнение хеш-таблицы элементами массива
for (i = 0; i < maxnum; i++)
insertData(a[i]);
// поиск элементов массива по хеш-таблице
for (i = maxnum-1; i >= 0; i--)
findData(a[i]);
// вывод элементов массива в файл List.txt
ofstream out("List.txt");
for (i = 0; i < maxnum; i++){
out << a[i];
if ( i < maxnum - 1 ) out << "\t";
}
out.close();
// сохранение хеш-таблицы в файл HashTable.txt
out.open("HashTable.txt");
for (i = 0; i < hashTableSize; i++){
out << i << " : " << used[i] << " : " << hashTable[i] << endl;
}
out.close();
// очистка хеш-таблицы
for (i = maxnum-1; i >= 0; i--) {
deleteData(a[i]);
}
system("pause");
return 0;
}
// хеш-функция размещения величины
hashTableIndex myhash(T data) {
return (data % hashTableSize);
}
// функция поиска местоположения и вставки величины в таблицу
void insertData(T data) {
hashTableIndex bucket;
bucket = myhash(data);
while ( used[bucket] hashTable[bucket] != data)
bucket = (bucket + 1) % hashTableSize;
if ( !used[bucket] ) {
used[bucket] = true;
hashTable[bucket] = data;
}
}
// функция поиска величины, равной data
bool findData (T data) {
hashTableIndex bucket;
bucket = myhash(data);
while ( used[bucket] hashTable[bucket] != data )
bucket = (bucket + 1) % hashTableSize;
return used[bucket] hashTable[bucket] == data;
}
//функция удаления величины из таблицы
void deleteData(T data){
int bucket, gap;
bucket = myhash(data);
while ( used[bucket] hashTable[bucket] != data )
bucket = (bucket + 1) % hashTableSize;
if ( used[bucket] hashTable[bucket] == data ){
used[bucket] = false;
gap = bucket;
bucket = (bucket + 1) % hashTableSize;
while ( used[bucket] ){
if ( bucket == myhash(hashTable[bucket]) )
bucket = (bucket + 1) % hashTableSize;
else if ( dist(myhash(hashTable[bucket]),bucket) < dist(gap,bucket) )
bucket = (bucket + 1) % hashTableSize;
else {
used[gap] = true;
hashTable[gap] = hashTable[bucket];
used[bucket] = false;
gap = bucket;
bucket++;
}
}
}
}
// функция вычисления расстояние от a до b (по часовой стрелке, слева направо)
int dist (hashTableIndex a,hashTableIndex b){
return (b - a + hashTableSize) % hashTableSize;
}
До сих пор рассматривались способы поиска в таблице по ключам, позволяющим однозначно идентифицировать запись. Такие ключи называются первичными. Возможен вариант организации таблицы, при котором отдельный ключ не позволяет однозначно идентифицировать запись. Такая ситуация часто встречается в базах данных. Идентификация записи осуществляется по некоторой совокупности ключей. Ключи, не позволяющие однозначно идентифицировать запись в таблице, называются вторичными ключами. Даже при наличии первичного ключа, для поиска записи могут быть использованы вторичные.
Идея
Вторичные ключи – это ключи, не позволяющие однозначно идентифицировать запись в таблице.
Закрытое хеширование или Метод открытой адресации – это технология разрешения
Коллизия – это ситуация, когда разным ключам соответствует одно значение
Коэффициент заполнения хеш-таблицы – это количество хранимых элементов массива, деленное на число возможных значений
Открытое хеширование или Метод цепочек – это технология разрешения
Первичные ключи – это ключи, позволяющие однозначно идентифицировать запись.
Повторное хеширование – это поиск местоположения для очередного элемента таблицы с учетом шага перемещения.
Пространство записей – это множество тех ячеек памяти, которые выделяются для хранения таблицы.
Пространство ключей – это множество всех теоретически возможных значений ключей записи.
Синонимы – это совпадающие ключи в хеш-таблице.
Хеширование – это преобразование входного массива данных определенного типа и произвольной длины в выходную битовую строку фиксированной длины.
Хеш-таблица – это структура данных, реализующая интерфейс ассоциативного массива, то есть она позволяет хранить пары вида "ключ- значение" и выполнять три операции: операцию добавления новой пары, операцию поиска и операцию удаления пары по ключу.
Хеш-таблицы с прямой адресацией – это хеш-таблицы, использующие инъективные
Цель работы: изучить построение функции
При выполнении лабораторной работы для каждого задания требуется написать программу на языке С++, которая получает на данные с клавиатуры или из входного файла, выполняет их обработку в соответствии с требованиями задания и выводит результат в выходной файл. Для обработки данных необходимо реализовать функции алгоритмов
Теоретические сведения.
Ознакомьтесь с материалом лекции 38.
Задания к лабораторной работе.
Выполните приведенные ниже задания.
Указания к выполнению работы.
Каждое задание необходимо решить в соответствии с изученным алгоритмами
Следует реализовать каждое задание в соответствии с приведенными этапами:
Требования к отчету.
Отчет по лабораторной работе должен соответствовать следующей структуре.
Контрольные вопросы
Цель лекции: изучить построение функции
Процесс поиска данных в больших объемах информации сопряжен с временными затратами, которые обусловлены необходимостью просмотра и сравнения с ключом поиска значительного числа элементов. Сокращение поиска возможно осуществить путем
В настоящее время используется широко распространенный метод обеспечения быстрого доступа к информации, хранящейся во внешней памяти –
Хеширование (или хэширование, англ. hashing ) – это преобразование входного массива данных определенного типа и произвольной длины в выходную битовую строку фиксированной длины. Такие преобразования также называются хеш-функциями или функциями
Хеш-таблица – это структура данных, реализующая интерфейс ассоциативного массива, то есть она позволяет хранить пары вида "ключ- значение" и выполнять три операции: операцию добавления новой пары, операцию поиска и операцию удаления пары по ключу. Хеш-таблица является массивом, формируемым в определенном порядке
Принято считать, что хорошей, с точки зрения практического применения, является такая
При этом первое свойство хорошей
Если бы все данные были случайными, то
При возникновении
Хеш-таблицы должны соответствовать следующим свойствам.
O(1). Однако при такой оценке не учитываются возможные аппаратные затраты на перестройку индекса хеш-таблицы, связанную с увеличением значения размера массива и добавлением в хеш-таблицу новой пары.Метод цепочек. Технология сцепления элементов состоит в том, что
(рис 38.1) Разрешение коллизий при помощи цепочекКаждая ячейка массива является указателем на связный список (цепочку) пар ключ-значение, соответствующих одному и тому же хеш-значению ключа.
Операции поиска или удаления данных требуют просмотра всех элементов соответствующей ему цепочки, чтобы найти в ней элемент с заданным ключом. Для добавления данных нужно добавить элемент в конец или начало соответствующего списка, и, в случае если коэффициент заполнения станет слишком велик, увеличить размер массива и перестроить таблицу.
При предположении, что каждый элемент может попасть в любую позицию таблицы с равной вероятностью и независимо от того, куда попал любой другой элемент, среднее время работы операции поиска элемента составляет O(1+k), где k – коэффициент заполнения таблицы.
Метод открытой адресации. В отличие от NULL.
В этом случае, если ячейка с вычисленным индексом занята, то можно просто просматривать следующие записи таблицы по порядку до тех пор, пока не будет найден ключ разрешение
(рис 38.2) Разрешение коллизий при помощи открытой адресацииПри любом методе разрешения
Для успешной работы алгоритмов поиска, последовательность проб должна быть такой, чтобы все ячейки хеш-таблицы оказались просмотренными ровно по одному разу.
Удаление элементов в такой схеме несколько затруднено. Обычно поступают так: заводят логический флаг для каждой ячейки, помечающий, удален ли элемент в ней или нет. Тогда удаление элемента состоит в установке этого флага для соответствующей ячейки хеш-таблицы, но при этом необходимо модифицировать процедуру поиска существующего элемента так, чтобы она считала удаленные ячейки занятыми, а процедуру добавления – чтобы она их считала свободными и сбрасывала значение флага при добавлении.
Существует несколько типов функций
Простейшей организацией таблицы, обеспечивающей идеально быстрый поиск, является таблица прямого доступа. В такой таблице ключ является адресом записи в таблице или может быть преобразован в адрес, причем таким образом, что никакие два разных ключа не преобразуются в один и тот же адрес. При
Назовем пространством ключей множество всех теоретически возможных значений ключей записи. Назовем пространством записей множество тех ячеек памяти, которые выделяются для хранения таблицы. Таблицы прямого доступа применимы только для таких задач, в которых размер пространства записей может быть равен размеру пространства ключей. В большинстве реальных задач размер пространства записей много меньше, чем пространства ключей. Так, если в качестве ключа используется фамилия, то, даже ограничив
В целях экономии памяти можно назначать размер пространства записей равным размеру фактического множества записей или превосходящим его незначительно. В этом случае необходимо иметь некоторую функцию, обеспечивающую отображение точки из пространства ключей в точку в пространстве записей, то есть, преобразование ключа в адрес записи: a=h(k), где a – адрес, k – ключ.
Идеальной
Простейшей HashTableSize. Результат интерпретируется как адрес записи. Следует иметь в виду, что такая функция хорошо соответствует первому, но плохо – последним трем требованиям к
Если ключей меньше, чем элементов массива, то в качестве Key на HashTableSize, то есть:
Key % HashTableSize
Данная функция очень проста, хотя и не относится к хорошим. Вообще, можно использовать любую HashTableSize.
На практике, метод деления – самый распространенный.
//функция создания хеш-таблицы метод деления по модулю
int Hash(int Key, int HashTableSize) {
//HashTableSize
return Key % HashTableSize;
}
Следующей
Еще одной
В качестве P, интерпретируется как число в Q>P. Обычно выбирают Q=P+1. Это число переводится из системы Q обратно в систему P, приводится к размеру пространства записей и интерпретируется как адрес.
Основная идея базовой структуры при открытом (внешнем) В классов, пронумерованных от 0 до В-1, строится h(x) такая, что для любого элемента х исходного множества функция h(x) принимает целочисленное значение из интервала 0,1,...,В-1, соответствующее классу, которому принадлежит элемент х. Часто классы называют сегментами, поэтому будем говорить, что элемент х принадлежит сегменту h(x). Массив, называемый таблицей сегментов и проиндексированный номерами сегментов 0,1,...,В-1, содержит заголовки для B списков. Элемент х, относящийся к i -му списку – это элемент исходного множества, для которого h(x)=i.
Если сегменты примерно одинаковы по размеру, то в этом случае списки всех сегментов должны быть наиболее короткими при данном числе сегментов. Если исходное множество состоит из N элементов, тогда средняя длина списков будет N/B элементов. Если можно оценить величину N и выбрать В как можно ближе к этой величине, то в каждом списке будет один или два элемента. Тогда время выполнения операторов словарей будет малой постоянной величиной, не зависящей от N.
Пример 1.
#include "stdafx.h"
#include <iostream>
#include <fstream>
using namespace std;
typedef int T; // тип элементов
typedef int hashTableIndex; // индекс в хеш-таблице
#define compEQ(a,b) (a == b)
typedef struct Node_ {
T data;// данные, хранящиеся в вершине
struct Node_ *next; // следующая вершина
} Node;
Node **hashTable;
int hashTableSize;
hashTableIndex myhash(T data);
Node *insertNode(T data);
void deleteNode(T data);
Node *findNode (T data);
int _tmain(int argc, _TCHAR* argv[]){
int i, *a, maxnum;
cout << "Введите количество элементов maxnum : ";
cin >> maxnum;
cout << "Введите размер хеш-таблицы HashTableSize : ";
cin >> hashTableSize;
a = new int[maxnum];
hashTable = new Node*[hashTableSize];
for (i = 0; i < hashTableSize; i++)
hashTable[i] = NULL;
// генерация массива
for (i = 0; i < maxnum; i++)
a[i] = rand();
// заполнение хеш-таблицы элементами массива
for (i = 0; i < maxnum; i++) {
insertNode(a[i]);
}
// поиск элементов массива по хеш-таблице
for (i = maxnum-1; i >= 0; i--) {
findNode(a[i]);
}
// вывод элементов массива в файл List.txt
ofstream out("List.txt");
for (i = 0; i < maxnum; i++){
out << a[i];
if ( i < maxnum - 1 ) out << "\t";
}
out.close();
// сохранение хеш-таблицы в файл HashTable.txt
out.open("HashTable.txt");
for (i = 0; i < hashTableSize; i++){
out << i << " : ";
Node *Temp = hashTable[i];
while ( Temp ){
out << Temp->data << " -> ";
Temp = Temp->next;
}
out << endl;
}
out.close();
// очистка хеш-таблицы
for (i = maxnum-1; i >= 0; i--) {
deleteNode(a[i]);
}
system("pause");
return 0;
}
// хеш-функция размещения вершины
hashTableIndex myhash(T data) {
return (data % hashTableSize);
}
// функция поиска местоположения и вставки вершины в таблицу
Node *insertNode(T data) {
Node *p, *p0;
hashTableIndex bucket;
// вставка вершины в начало списка
bucket = myhash(data);
if ((p = new Node) == 0) {
fprintf (stderr, "Нехватка памяти (insertNode)\n");
exit(1);
}
p0 = hashTable[bucket];
hashTable[bucket] = p;
p->next = p0;
p->data = data;
return p;
}
//функция удаления вершины из таблицы
void deleteNode(T data) {
Node *p0, *p;
hashTableIndex bucket;
p0 = 0;
bucket = myhash(data);
p = hashTable[bucket];
while (p !compEQ(p->data, data)) {
p0 = p;
p = p->next;
}
if (!p) return;
if (p0)
p0->next = p->next;
else
hashTable[bucket] = p->next;
free (p);
}
// функция поиска вершины со значением data
Node *findNode (T data) {
Node *p;
p = hashTable[myhash(data)];
while (p !compEQ(p->data, data))
p = p->next;
return p;
}
При закрытом (внутреннем) х в сегмент с номером h(х), который уже занят другим элементом (коллизия), то в соответствии с методикой повторного h1(х),h2(х),..., куда можно поместить элемент х. Каждое из этих местоположений последовательно проверяется, пока не будет найдено свободное. Если свободных сегментов нет, то, следовательно, таблица заполнена, и элемент х добавить нельзя.
При поиске элемента х необходимо просмотреть все местоположения h(x),h1(х),h2(х),..., пока не будет найден х или пока не встретится пустой сегмент. Чтобы объяснить, почему можно остановить поиск при достижении пустого сегмента, предположим, что в хеш-таблице не допускается удаление элементов. Пусть h3(х) – первый пустой сегмент. В такой ситуации невозможно нахождение элемента х в сегментах h4(х),h5(х) и далее, так как при вставке элемент х вставляется в первый пустой сегмент, следовательно, он находится где-то до сегмента h3(х). Но если в хеш-таблице допускается удаление элементов, то при достижении пустого сегмента, не найдя элемента х, нельзя быть уверенным в том, что его вообще нет в таблице, так как сегмент может стать пустым уже после вставки элемента х.
Поэтому, чтобы увеличить эффективность данной реализации, необходимо в сегмент, который освободился после . В качестве альтернативы специальной константе можно использовать дополнительное поле таблицы, которое показывает состояние элемента. Важно различать константы и NULL – последняя находится в сегментах, которые никогда не содержали элементов. При таком подходе выполнение поиска элемента не требует просмотра всей хеш-таблицы. Кроме того, при вставке элементов сегменты, помеченные константой , можно трактовать как свободные, таким образом, пространство, освобожденное после удаления элементов, можно рано или поздно использовать повторно. Но если невозможно непосредственно сразу после удаления элементов пометить освободившиеся сегменты, то следует предпочесть закрытому
Существует несколько методов повторного h(x),h1(х),h2(х),...:
Линейное опробование сводится к
адрес=h(x)+ci,
где i – номер попытки разрешить коллизию;
c – константа, определяющая шаг перебора.
При шаге, равном единице, происходит последовательный перебор всех сегментов после текущего. Квадратичное опробование отличается от линейного тем, что шаг перебора сегментов нелинейно зависит от номера попытки найти свободный сегмент:
адрес=h(x)+ci+di2,
где i – номер попытки разрешить коллизию,
c и d – константы.
Благодаря нелинейности такой адресации уменьшается число проб при большом числе ключей-синонимов. Однако даже относительно небольшое число проб может быстро привести к выходу за адресное пространство небольшой таблицы вследствие квадратичной зависимости адреса от номера попытки.
Еще одна разновидность метода открытой адресации, которая называется двойным хешированием, основана на нелинейной адресации, достигаемой за счет суммирования значений основной и дополнительной хеш-функций:
адрес=h(x)+ih2(x).
Очевидно, что по мере заполнения хеш-таблицы будут происходить
Однако в случае многократного превышения
В случае применения схемы закрытого
Как только несколько последовательных сегментов будут заполнены, образуя группу, любой новый элемент при попытке вставки в эти сегменты будет вставлен в конец этой группы, увеличивая тем самым длину группы последовательно заполненных сегментов. Другими словами, для поиска пустого сегмента в случае непрерывного расположения заполненных сегментов необходимо просмотреть больше сегментов, чем при случайном распределении заполненных сегментов. Отсюда также следует очевидный вывод, что при непрерывном расположении заполненных сегментов увеличивается время выполнения вставки нового элемента и других операций.
Пример 2.
#include "stdafx.h"
#include <iostream>
#include <fstream>
using namespace std;
typedef int T; // тип элементов
typedef int hashTableIndex;// индекс в хеш-таблице
int hashTableSize;
T *hashTable;
bool *used;
hashTableIndex myhash(T data);
void insertData(T data);
void deleteData(T data);
bool findData (T data);
int dist (hashTableIndex a,hashTableIndex b);
int _tmain(int argc, _TCHAR* argv[]){
int i, *a, maxnum;
cout << "Введите количество элементов maxnum : ";
cin >> maxnum;
cout << "Введите размер хеш-таблицы hashTableSize : ";
cin >> hashTableSize;
a = new int[maxnum];
hashTable = new T[hashTableSize];
used = new bool[hashTableSize];
for (i = 0; i < hashTableSize; i++){
hashTable[i] = 0;
used[i] = false;
}
// генерация массива
for (i = 0; i < maxnum; i++)
a[i] = rand();
// заполнение хеш-таблицы элементами массива
for (i = 0; i < maxnum; i++)
insertData(a[i]);
// поиск элементов массива по хеш-таблице
for (i = maxnum-1; i >= 0; i--)
findData(a[i]);
// вывод элементов массива в файл List.txt
ofstream out("List.txt");
for (i = 0; i < maxnum; i++){
out << a[i];
if ( i < maxnum - 1 ) out << "\t";
}
out.close();
// сохранение хеш-таблицы в файл HashTable.txt
out.open("HashTable.txt");
for (i = 0; i < hashTableSize; i++){
out << i << " : " << used[i] << " : " << hashTable[i] << endl;
}
out.close();
// очистка хеш-таблицы
for (i = maxnum-1; i >= 0; i--) {
deleteData(a[i]);
}
system("pause");
return 0;
}
// хеш-функция размещения величины
hashTableIndex myhash(T data) {
return (data % hashTableSize);
}
// функция поиска местоположения и вставки величины в таблицу
void insertData(T data) {
hashTableIndex bucket;
bucket = myhash(data);
while ( used[bucket] hashTable[bucket] != data)
bucket = (bucket + 1) % hashTableSize;
if ( !used[bucket] ) {
used[bucket] = true;
hashTable[bucket] = data;
}
}
// функция поиска величины, равной data
bool findData (T data) {
hashTableIndex bucket;
bucket = myhash(data);
while ( used[bucket] hashTable[bucket] != data )
bucket = (bucket + 1) % hashTableSize;
return used[bucket] hashTable[bucket] == data;
}
//функция удаления величины из таблицы
void deleteData(T data){
int bucket, gap;
bucket = myhash(data);
while ( used[bucket] hashTable[bucket] != data )
bucket = (bucket + 1) % hashTableSize;
if ( used[bucket] hashTable[bucket] == data ){
used[bucket] = false;
gap = bucket;
bucket = (bucket + 1) % hashTableSize;
while ( used[bucket] ){
if ( bucket == myhash(hashTable[bucket]) )
bucket = (bucket + 1) % hashTableSize;
else if ( dist(myhash(hashTable[bucket]),bucket) < dist(gap,bucket) )
bucket = (bucket + 1) % hashTableSize;
else {
used[gap] = true;
hashTable[gap] = hashTable[bucket];
used[bucket] = false;
gap = bucket;
bucket++;
}
}
}
}
// функция вычисления расстояние от a до b (по часовой стрелке, слева направо)
int dist (hashTableIndex a,hashTableIndex b){
return (b - a + hashTableSize) % hashTableSize;
}
До сих пор рассматривались способы поиска в таблице по ключам, позволяющим однозначно идентифицировать запись. Такие ключи называются первичными. Возможен вариант организации таблицы, при котором отдельный ключ не позволяет однозначно идентифицировать запись. Такая ситуация часто встречается в базах данных. Идентификация записи осуществляется по некоторой совокупности ключей. Ключи, не позволяющие однозначно идентифицировать запись в таблице, называются вторичными ключами. Даже при наличии первичного ключа, для поиска записи могут быть использованы вторичные.
Идея
Вторичные ключи – это ключи, не позволяющие однозначно идентифицировать запись в таблице.
Закрытое хеширование или Метод открытой адресации – это технология разрешения
Коллизия – это ситуация, когда разным ключам соответствует одно значение
Коэффициент заполнения хеш-таблицы – это количество хранимых элементов массива, деленное на число возможных значений
Открытое хеширование или Метод цепочек – это технология разрешения
Первичные ключи – это ключи, позволяющие однозначно идентифицировать запись.
Повторное хеширование – это поиск местоположения для очередного элемента таблицы с учетом шага перемещения.
Пространство записей – это множество тех ячеек памяти, которые выделяются для хранения таблицы.
Пространство ключей – это множество всех теоретически возможных значений ключей записи.
Синонимы – это совпадающие ключи в хеш-таблице.
Хеширование – это преобразование входного массива данных определенного типа и произвольной длины в выходную битовую строку фиксированной длины.
Хеш-таблица – это структура данных, реализующая интерфейс ассоциативного массива, то есть она позволяет хранить пары вида "ключ- значение" и выполнять три операции: операцию добавления новой пары, операцию поиска и операцию удаления пары по ключу.
Хеш-таблицы с прямой адресацией – это хеш-таблицы, использующие инъективные
Цель работы: изучить построение функции
При выполнении лабораторной работы для каждого задания требуется написать программу на языке С++, которая получает на данные с клавиатуры или из входного файла, выполняет их обработку в соответствии с требованиями задания и выводит результат в выходной файл. Для обработки данных необходимо реализовать функции алгоритмов
Теоретические сведения.
Ознакомьтесь с материалом лекции 38.
Задания к лабораторной работе.
Выполните приведенные ниже задания.
Указания к выполнению работы.
Каждое задание необходимо решить в соответствии с изученным алгоритмами
Следует реализовать каждое задание в соответствии с приведенными этапами:
Требования к отчету.
Отчет по лабораторной работе должен соответствовать следующей структуре.
Контрольные вопросы
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.