
К9-12В. Вопросы и ответы к ГОСам 2013 / Программирование на языке высокого уровня / 09. Перемешанная таблица, использующая перемешиванием сцеплением. Определение, хэш-функция, возникающие проблемы, основн
.docx09. Перемешанная таблица, использующая перемешиванием сцеплением. Определение, хэш-функция, возникающие проблемы, основные операции, особенности их реализации.
Перемешанные таблицы
Поскольку
взаимную однозначность преобразования
ключа в адрес хранения элемента таблицы
в общем случае обеспечить практически
невозможно, от требования взаимной
однозначности отказываются. Это приводит
к тому, что для некоторых kikj
возможна ситуация, что I(ki) = I(kj).
Такая ситуация создает переполнение
позиций отображаемого вектора и носит
название коллизии. Чтобы таких
ситуаций было меньше, функцию расстановки
подбирают из условия возможно более
равномерного отображения ключей в
адреса хранения. Таблицы, построенные
по такому принципу, также являются
таблицами с вычисляемыми входами и
называются перемешаннымитаблицами.
Перемешанную
таблицу можно заполнять и обращаться
с ней как с таблицей произвольного
доступа до тех пор, пока не встретится
ключ kj такой, что kikj,
но I(ki) = I(kj), причем ki
– это ключ, который уже встречался ранее
(т.е. пока не возникнет коллизия). Элемент
с ключом ki уже был помещен в позицию
(элемент вектора с индексом) I(ki).
Проблема состоит в определении места
для хранения нового элемента таблицы
с ключом kj. Решение этой проблемы
предполагает разрешение коллизии
и носит название перемешивания.
Существуют различные вспомогательные
методы перемешивания, зависящие от
того, какой способ используется для
разрешения коллизии.
Часто перемешанные таблицы называют еще хэш таблицами (от английского hash–мешанина, путаница), функции расстановки – хэш функциями, а нахождение места хранения элемента – хешированием.
В перемешанной таблице можно выделить две области: основную, в которую элементы таблицы отображаются в результате вычисления производного ключа, и область переполнения, в которую попадают элементы в результате перемешивания при обнаружении коллизии. В зависимости от используемого способа перемешивания такое разделение может быть явным или скрытым. В любом случае основная область перемешанной таблицы отображается вектором. Область переполнения может быть отображена и вектором, и списком (точнее, семейством списков), в зависимости от того, какой способ перемешивания используется.
В каждом конкретном случае при использовании перемешанной таблицы используется какой-то один способ перемешивания. Он используется как при поиске элемента в таблице, так и при включении в таблицу нового элемента.
Рассмотрим два способа перемешивания, используемых наиболее часто: открытое перемешивание (этот способ называют еще перемешиванием сложением) и перемешивание сцеплением.
Перемешивание сцеплением
Перемешанная таблица, использующая перемешивание сцеплением, обладает следующими свойствами:
-
Таблица явно разделяется на две области – основную область и область переполнения.
-
Основная область таблицы отображается вектором, размер которого должен быть достаточным для отображения всех значений ключей.
-
Область переполнения представляет собой семейство списков – отдельный список для каждого элемента основной области.
Следовательно, элемент перемешанной таблицы, использующей перемешивание сцеплением, должен иметь еще одно поле – поле связи с элементами из области переполнения (рис. II-49).
Рассмотрим способ перемешивания сцеплением на примере выполнения операции включения элементов в таблицу.
Пусть дана та же, что и ранее, последовательность ключей элементов, записываемых в таблицу (всего 15 элементов):
12, 48, 3, 5, 7, 63, 15, 202, 103, 188, 30, 43, 6, 18, 19
Используем ту же функцию расстановки:
I(k) = k%10;
В соответствии с выбранной функцией расстановки основная область таблицы будет представлена вектором из 10 элементов.
Функция расстановки отобразит исходное множество ключей в следующее множество производных ключей:
номер элемента |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
исходный ключ |
12 |
48 |
3 |
5 |
7 |
63 |
15 |
202 |
103 |
188 |
30 |
43 |
6 |
18 |
19 |
производный ключ |
2 |
8 |
3 |
5 |
7 |
3 |
5 |
2 |
3 |
8 |
0 |
3 |
6 |
8 |
9 |
При занесении записей в таблицу, пока не встретится коллизия (до записи с ключом 63), записи будут включаться в основную область. В случае коллизии запись будет включена в список, связанный с соответствующим элементом основной таблицы, причем переполняющая запись может быть включена и в начало, и в конец соответствующего списка.
В результате занесения всех перечисленных записей таблица примет вид, приведенный на рис. II-50 (для наглядности переполняющие записи включаются в конец соответствующего списка).
Из приведенного примера видно, что записи, ключи которых имеют одинаковое значение производного ключа, объединяются в один список; следовательно, при поиске записи потребуется просматривать только записи таблицы с одинаковым значением производного ключа. В цитировавшейся выше книге Ф.Хопгуда (Методы компиляции, стр. 36) приведена следующая оценка средней длины поиска для случайных, равномерно распределенных записей:
где M – размер основной области таблицы (отображающего вектора),
N – общее количество элементов в таблице.
На практике отображающий вектор (основную область таблицы) удобно представить только массивом указателей на элементы таблицы, а все записи включать в область переполнения. В этом случае все операции с таблицей сначала осуществляют выбор необходимого списка (элемента массива указателей), а затем реализуется требуемая операция со списком (соответствующие операции со списками были рассмотрены ранее). Приведенная выше таблица в таком случае будет представлена следующим образом:
Предлагаю самостоятельно разработать и реализовать основные операции работы с перемешанной таблицей, использующей перемешивание сцеплением.
struct overflowTableItem{
int key;
int release;
size_t sizeinfo;
long position;
overflowTableItem *next;
};
overflowTableItem *table[MAX_ITEM];
int hashFunction(int key){
return key % MAX_ITEM;
}
for(int i=0; i < MAX_ITEM; i++)
table[i] = NULL;
int tableInsert(overflowTableItem *table[],int key, string info){
int position = hashFunction(key);
int release;
overflowTableItem *tmpItem, *lastItem = NULL;
overflowTableItem *item = new overflowTableItem;
item->key = key;
item->info = info;
if(table[position] == NULL){
item->release = 1;
item->next = NULL;
table[position] = item;
}
else{
tmpItem = table[position];
release = 0;
while(tmpItem){
if(tmpItem->key == key){
release = tmpItem->release;
if(tmpItem->next != NULL && tmpItem->next->key != key) break;
}
if(tmpItem->next == NULL) break;
tmpItem = tmpItem->next;
}
item->release = ++release;
item->next = tmpItem->next;
tmpItem->next = item;
}
return 0;
}
int findByKey(overflowTableItem *table[],int key){
int position = hashFunction(key);
bool flag = false;
overflowTableItem *tmpItem;
if(table[position] == NULL){
cout << endl << "not found" << endl;
return 1;
}
else{
tmpItem = table[position];
while(tmpItem){
if(tmpItem->key == key){
cout << endl << "[k: " << tmpItem->key << " r: " << tmpItem->release << " i: " << tmpItem->info << "] ";
flag = true;
}
tmpItem = tmpItem->next;
}
if(!flag) cout << endl << "not found";
cout << endl;
}
return 0;
}
int findByKeyAndRelease(overflowTableItem *table[], int key, int release){
int position = hashFunction(key);
bool flag = false;
overflowTableItem *tmpItem;
if(table[position] == NULL){
cout << endl << "not found" << endl;
return 1;
}
else{
tmpItem = table[position];
while(tmpItem){
if(tmpItem->key == key && tmpItem->release == release){
cout << endl << "[k: " << tmpItem->key << " r: " << tmpItem->release << " i: " << tmpItem->info << "] " << endl;;
flag = true;
return 0;
}
tmpItem = tmpItem->next;
}
if(!flag) cout << endl << "not found";
cout << endl;
}
return 0;
}
void viewTable(overflowTableItem *table[]){
overflowTableItem *tmpItem;
cout << endl;
for(int i=0; i<MAX_ITEM; i++){
if(table[i]){
cout << "[" << i << "]";
tmpItem = table[i];
while(tmpItem){
cout << " -> [k: " << tmpItem->key << " r: " << tmpItem->release << " i: " << tmpItem->info << "] ";
tmpItem = tmpItem->next;
}
cout << endl;
}
else cout << "[" << i << "] " << "empty" << endl;
}
}
void clearTable(overflowTableItem *table[]){
overflowTableItem *tmpItem, *prevItem = NULL, *trash;
for(int i=0; i<MAX_ITEM; i++){
if(table[i]){
tmpItem = table[i];
prevItem = NULL;
while(tmpItem){
if(tmpItem->next != NULL && tmpItem->next->key == tmpItem->key){
if(prevItem == NULL){
table[i] = tmpItem->next;
trash = tmpItem;
tmpItem = tmpItem->next;
delete trash;
continue;
}
else{
trash = tmpItem;
prevItem->next = tmpItem->next;
tmpItem = tmpItem->next;
delete trash;
continue;
}
}
prevItem = tmpItem;
tmpItem = tmpItem->next;
}
}
}
}