Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

К9-12В. Вопросы и ответы к ГОСам 2013 / Программирование на языке высокого уровня / 08. Перемешанная таблица, использующая перемешиванием сложением. Определение, хэш-функция, возникающие проблемы, основны

.docx
Скачиваний:
169
Добавлен:
10.05.2014
Размер:
104.51 Кб
Скачать

08. Перемешанная таблица, использующая перемешиванием сложением. Определение, хэш-функция, возникающие проблемы, основные операции, особенности их реализации.

Перемешанные таблицы

Поскольку взаимную однозначность преобразования ключа в адрес хранения элемента таблицы в общем случае обеспечить практически невозможно, от требования взаимной однозначности отказываются. Это приводит к тому, что для некоторых kikj возможна ситуация, что I(ki) = I(kj). Такая ситуация создает переполнение позиций отображаемого вектора и носит название коллизии. Чтобы таких ситуаций было меньше, функцию расстановки подбирают из условия возможно более равномерного отображения ключей в адреса хранения. Таблицы, построенные по такому принципу, также являются таблицами с вычисляемыми входами и называются перемешанными таблицами.

Перемешанную таблицу можно заполнять и обращаться с ней как с таблицей произвольного доступа до тех пор, пока не встретится ключ kj такой, что kikj, но I(ki) = I(kj), причем ki – это ключ, который уже встречался ранее (т.е. пока не возникнет коллизия). Элемент с ключом ki уже был помещен в позицию (элемент вектора с индексом) I(ki). Проблема состоит в определении места для хранения нового элемента таблицы с ключом kj. Решение этой проблемы предполагает разрешение коллизии и носит название перемешивания. Существуют различные вспомогательные методы перемешивания, зависящие от того, какой способ используется для разрешения коллизии.

Часто перемешанные таблицы называют еще хэш таблицами (от английского hash–мешанина, путаница), функции расстановки – хэш функциями, а нахождение места хранения элемента – хешированием.

В перемешанной таблице можно выделить две области: основную, в которую элементы таблицы отображаются в результате вычисления производного ключа, и область переполнения, в которую попадают элементы в результате перемешивания при обнаружении коллизии. В зависимости от используемого способа перемешивания такое разделение может быть явным или скрытым. В любом случае основная область перемешанной таблицы отображается вектором. Область переполнения может быть отображена и вектором, и списком (точнее, семейством списков), в зависимости от того, какой способ перемешивания используется.

В каждом конкретном случае при использовании перемешанной таблицы используется какой-то один способ перемешивания. Он используется как при поиске элемента в таблице, так и при включении в таблицу нового элемента.

Рассмотрим два способа перемешивания, используемых наиболее часто: открытое перемешивание (этот способ называют еще перемешиванием сложением) и перемешивание сцеплением.

Открытое перемешивание

Перемешанная таблица, использующая открытое перемешивание (или перемешивание сложением), обладает следующими свойствами:

  1. Вся перемешанная таблица отображается вектором.

  2. Размер таблицы (вектора) должен быть достаточным для размещения всех элементов таблицы (что очевидно) и отображения их ключей (так как значение ключа отображается в индекс вектора).

  3. Разделение таблицы на две области является весьма условным и зависит от конкретных значений ключей включаемых в таблицу элементов и порядка их включения в таблицу (в одной ситуации некоторый элемент вектора может быть отнесен к основной области, в другой – этот же элемент принадлежит области переполнения).

Рассмотрим способ открытого перемешивания на примере выполнения операции включения элементов в таблицу.

Пусть дана следующая последовательность ключей элементов, записываемых в таблицу (всего 15 элементов):

12, 48, 3, 5, 7, 63, 15, 202, 103, 188, 30, 43, 6, 18, 19

Прежде всего, выберем функцию расстановки. Возьмем для примера функцию

I(k) = k%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

Пусть размер таблицы равен M = 14 элементам (размер выбран так, чтобы показать реакцию на переполнение таблицы). В исходном состоянии таблица пуста.

При занесении элемента в таблицу, прежде всего, вычисляется производный ключ и анализируется позиция таблицы, определенная значением производного ключа. Если эта позиция свободна (что имеет место для первых пяти элементов), элемент заносится в нее (в основную область), и для занесения потребуется только одно обращение к таблице. Таким образом, после записи в таблицу первых пяти элементов таблица будет иметь вид, представленный на рис. II-46,а.

Если позиция таблицы занята (что имеет место, например, при записи в таблицу элемента с ключом 63), тогда начинается процесс перемешивания. Последовательно выбираются следующие позиции таблицы с целью найти свободную. Определение "открытое перемешивание" обусловлено тем свойством, что элементы таблицы, просматриваемые, начиная с конкретной позиции, являются открытыми для ключей, отображаемых в другие позиции. Как только свободная позиция будет найдена, элемент записывается в нее, и эта позиция считается принадлежащей области переполнения (рис. II-46,b; позиции таблицы, относящиеся к области переполнения, выделены цветом). В соответствии с описанным выше алгоритмом в таблицу будут записаны первые 14 элементов, после чего таблица примет вид, представленный на рис. II-46,c.

Если же при поиске свободной позиции мы возвращаемся в исходную точку, что происходит при попытке записать в таблицу последний элемент с ключом 19 (рис. II-46,c), это означает, что в таблице нет свободного места.

В результате для занесения элемента в таблицу в общем случае требуется несколько обращений. Например, при занесении в таблицу элементов с ключами 63 и 15 потребуется по 2 обращения к таблице, а при занесении элемента с ключом 202 – 8 обращений. Количество обращений при занесении в таблицу каждого элементов представлено ниже:

исходный ключ

12

48

3

5

7

63

15

202

103

188

30

43

6

18

производный ключ

2

8

3

5

7

3

5

2

3

8

0

3

6

8

количество обращений

1

1

1

1

1

2

2

8

8

4

1

10

8

8

Суммарное количество обращений к таблице равно 56, среднее – 56/14 =4

Важный результат, который всегда необходимо учитывать при построении функции расстановки, состоит в том, что для случайных записей средняя длина поиска минимизируется, если функция расстановки отображает одинаковое количество ключей на все позиции таблицы-вектора от 1 до M. Вообще говоря, это условие может оказаться невыполнимым по разным причинам (в приведенном выше примере это условие не выполняется). В таких случаях следует, насколько это возможно, приблизить отображение к такой желаемой форме (лучшим вариантом функции расстановки была бы функция I(k) = k % M).

В приведенном выше примере скопление записей образовалось после третьей позиции, в результате чего для занесения элемента с ключом 202 потребовалось 8 обращений к таблице. Подобное скопление неизбежно при использовании открытого перемешивания. Можно несколько улучшить результаты, если переопределить понятие следующей позиции таблицы. В общем случае использование открытого перемешивания при определении следующей позиции требует увеличения индекса элемента на некоторую величину h, называемую шагом перемешивания (другими словами, индекс складывается с шагом перемешивания; поэтому открытое перемешивание называют еще перемешиванием сложением). Значение шага перемешивания может быть любым, но обязательно взаимно простым с величиной, определяющей размер таблицы, чтобы обеспечить просмотр всех ее позиций (например, для таблицы размером 14 элементов можно использовать шаг перемешивания 3 или 5, но нельзя 2 или 6). Выбор шага перемешивания может повлиять на количество обращений к таблице.

В приведенном выше примере был использован шаг перемешивания, равный 1. Если для той же последовательности ключей использовать другое значение шага перемешивания, например, 5, получим следующее количество обращений к таблице:

h = 5

исходный ключ

12

48

3

5

7

63

15

202

103

188

30

43

6

18

производный ключ

2

8

3

5

7

3

5

2

3

8

0

3

6

8

количество обращений

1

1

1

1

1

3

2

3

4

4

1

9

1

10

Суммарное количество обращений к таблице равно 42, среднее – 42/14=3

При занесении элементов в таблицу нужно иметь возможность определять, занята позиция таблицы или свободна. Для этого достаточно включить в элемент таблицы дополнительное поле занятости (по аналогии с динамической просматриваемой таблицей-вектором: значение 0 в этом поле указывает на то, что позиция свободна); тогда структура элемента таблицы может быть представлена следующим образом;

struct Item{

int busy; /* признак занятости позиции таблицы */

int key;

Type info;

};

Алгоритм занесения элемента в перемешанную таблицу с использованием открытого перемешивания приведен на рис. II-47.

Поиск элемента осуществляется по такому же правилу: вычисляется производный ключ и анализируется позиция таблицы, которая определяется значением производного ключа. Если искомый ключ находится в этой позиции, поиск завершен успешно, причем потребовалось только одно обращение к таблице. Если позиция свободна, поиск заканчивается не успешно – в таблице нет элемента с искомым ключом. Если позиция таблицы занята, но ключи не совпадают, выбирается и анализируется следующий элемент таблицы (с учетом используемого шага перемешивания), пока не будет найден искомый элемент или не будет обнаружена свободная позиция.

Из-за эффекта скопления записей, отмеченного выше, не так просто определить среднюю длину поиска в перемешанной таблице, использующей открытое перемешивание. Кроме того, средняя длина поиска будет зависеть также и от степени заполнения таблицы. В книге Ф.Хопгуда (см. Ф.Хопгуд. Методы компиляции. – М.: Мир, 1972. стр. 33) приведена следующая оценка средней длины поиска в предположении, что в скоплении все записи, отображенные в i-ю позицию, появились раньше записей, отображенных в (i+1)-ю позицию:

,

где – степень заполнения таблицы (M – размер таблицы, N – число записей, уже включенных в таблицу).

Важно отметить, что значения, полученные для средней длины поиска, не зависят от размера таблицы, а зависят только от того, насколько заполнена таблица. Даже для таблицы, заполненной на 80%, средняя длина поиска все еще равна приблизительно 3.

Если для таблицы определена операция удаления элемента, нужно иметь в виду следующее:

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

  2. При занесении в таблицу нового элемента удаленная позиция должна рассматриваться как свободная.

  3. При поиске элемента удаленная позиция не должна рассматриваться ни как свободная (иначе нельзя будет найти элементы, занесенные в таблицу перед удалением элемента и расположенные после удаленного элемента), ни как занятая; она должна просто пропускаться.

Алгоритм поиска элемента в перемешанной таблице, использующей открытое перемешивание, приведен на рис. II-48.

Алгоритм удаления элемента достаточно прост и здесь не приводится.

Функции занесения элемента и поиска элемента в перемешанной таблице приводятся ниже, а также в файле Programs/tab3vec.cpp.

struct Item{

int busy; /* 0 - позиция свободна, 1 - занята, (-1) - удалена */

int key;

Type info;

};

const int M = 100; /* размер таблицы */

Item ptable[M]; /* таблица */

/* хеш-функция */

int I(int k)

{

return k % M;

}

/* Занесение нового элемента в таблицу.

* Результат: 0, если элемент включен в таблицу,

* -1, если в таблице есть элемент с заданным ключом,

* -2, если в таблице нет свободного места

*/

int insert(int k, Type in)

{

int strt, i, h = 1; /* шаг перемешивания */

/* вычисление исходной позиции таблицы */

strt = i = I(k);

while(ptable[i].busy > 0){ /* позиция занята */

if(ptable[i].key == k)

return -1; /* элемент с заданным ключом есть в таблице */

i = (i+h) % M; /* следующая позиция */

if(i == strt)

return -2; /* вернулись в исходную позицию - таблица полна */

}

/* занесение нового элемента */

ptable[i].key = k;

ptable[i].busy = 1;

ptable[i].info = in;

return 0; /* элемент занесен */

}

/* Поиск элемента в таблице.

* Результат: индекс элемента таблицы, если элемент найден,

* -1, если в таблице нет элемента с заданным ключом

*/

int search(int k)

{

int strt, i, h = 1; /* шаг перемешивания */

/* вычисление исходной позиции таблицы */

strt = i = I(k);

while(ptable[i].busy>= 0){ /* позиция не свободна */

if(ptable[i].busy > 0 &&ptable[i].key == k)

return i; /* элемент найден */

i = (i+h) % M; /* следующая позиция */

if(i == strt)

break;

}

return -1; /* вернулись в исходную позицию – элемента в таблице нет */

}

Соседние файлы в папке Программирование на языке высокого уровня