Рассмотрите реализацию хэш-таблицы с открытой адресацией и двойным хешированием; дан фрагмент на C: typedef struct{int key; int used;} Entry; Entry table[SIZE]
Вы привели структуру записи: typedef struct { int key; int used; } Entry; Entry tableSIZESIZESIZE;
Ниже — разъяснение и пример корректной реализации хэш-таблицы с открытой адресацией и двойным хешированием doublehashingdouble hashingdoublehashing. Важно: поле used должно иметь три состояния EMPTY,OCCUPIED,DELETEDEMPTY, OCCUPIED, DELETEDEMPTY,OCCUPIED,DELETED, иначе удаление работать не будет корректно — при простом 0/1 вы потеряете возможность продолжать поиск через удалённые слоты.
Основные моменты
Две хеш-функции: h1kkk = k % SIZE, h2kkk должно быть ненулевым и по возможности взаимно простым с SIZE. Часто берут h2kkk = 1 + kk % (SIZE-1)k при простом SIZE.Индекс для i-й попытки: idx = h1(k)+i∗h2(k)h1(k) + i * h2(k)h1(k)+i∗h2(k) % SIZE.Поиск: если попали в EMPTY — ключа нет, можно остановиться; если OCCUPIED и ключ совпал — найден; если DELETED или OCCUPIED с другим ключом — продолжаем.Вставка: при обходе запоминаем индекс первой DELETED ячейки; если находим EMPTY — вставляем либо в первую DELETED еслибылаесли былаеслибыла, либо в этот EMPTY; если найден совпадающий ключ — можно обновить или отказать.Удаление: найденную OCCUPIED ячейку помечаем DELETED.
Пример кода наглядный,минимальныйнаглядный, минимальныйнаглядный,минимальный:
/ Поиск индекса ключа, возвращает индекс либо -1 если не найден / int searchintkeyint keyintkey { int start = h1keykeykey; int step = h2keykeykey; for (int i = 0, idx = start; i < SIZE; ++i, idx = (idx + step) % SIZE) { if table[idx].used==EMPTYtable[idx].used == EMPTYtable[idx].used==EMPTY return -1; / дальше пусто — нет ключа / if (table[idx].used == OCCUPIED && table[idx].key == key) return idx; / если DELETED или OCCUPIED другой ключ — продолжаем / } return -1; }
/ Вставка: возвращает 0 — успешно, 1 — ключ уже есть, -1 — таблица полна / int insertintkeyint keyintkey { int start = h1keykeykey; int step = h2keykeykey; int first_deleted = -1; for (int i = 0, idx = start; i < SIZE; ++i, idx = (idx + step) % SIZE) { if table[idx].used==OCCUPIEDtable[idx].used == OCCUPIEDtable[idx].used==OCCUPIED { if table[idx].key==keytable[idx].key == keytable[idx].key==key return 1; / уже есть / } else if table[idx].used==DELETEDtable[idx].used == DELETEDtable[idx].used==DELETED { if firstdeleted==−1first_deleted == -1firstdeleted==−1 first_deleted = idx; } else { / EMPTY / int pos = firstdeleted!=−1first_deleted != -1firstdeleted!=−1 ? first_deleted : idx; tablepospospos.key = key; tablepospospos.used = OCCUPIED; return 0; } } / если цикл кончился, таблица либо заполнена OCCUPIED, либо только DELETED; если был DELETED — вставляем туда / if firstdeleted!=−1first_deleted != -1firstdeleted!=−1 { tablefirstdeletedfirst_deletedfirstdeleted.key = key; tablefirstdeletedfirst_deletedfirstdeleted.used = OCCUPIED; return 0; } return -1; / полна / }
/ Удаление: возвращает 0 — успешно, -1 — не найден / int deleteintkeyint keyintkey { int idx = searchkeykeykey; if idx==−1idx == -1idx==−1 return -1; tableidxidxidx.used = DELETED; return 0; }
Замечания и рекомендации
Лучше, чтобы SIZE было простым числом — тогда формула h2 = 1 + kk % (SIZE-1)k гарантирует, что шаг и SIZE взаимно просты и весь массив будет пройден.Если не хотите трёх состояний, можно хранить особое значение ключа как «пусто», но это неудобно и ненадёжно для общих ключей.При интенсивных операциях удаления может расти число DELETED ячеек → периодически полезно делать rehash пересоздатьтаблицуивставитьтолькоOCCUPIEDэлементыпересоздать таблицу и вставить только OCCUPIED элементыпересоздатьтаблицуивставитьтолькоOCCUPIEDэлементы.Для более качественного хеширования используйте хорошие хеш-функции для int например,перемешиваниебитовнапример, перемешивание битовнапример,перемешиваниебитов перед применением модификаторов.
Если хотите, могу дать:
версию кода с обработкой ошибок и тестовым main;пример h2 для случая, когда SIZE — степень двойки;доказательство, почему нужно три состояния.
Вы привели структуру записи:
typedef struct { int key; int used; } Entry;
Entry tableSIZESIZESIZE;
Ниже — разъяснение и пример корректной реализации хэш-таблицы с открытой адресацией и двойным хешированием doublehashingdouble hashingdoublehashing. Важно: поле used должно иметь три состояния EMPTY,OCCUPIED,DELETEDEMPTY, OCCUPIED, DELETEDEMPTY,OCCUPIED,DELETED, иначе удаление работать не будет корректно — при простом 0/1 вы потеряете возможность продолжать поиск через удалённые слоты.
Основные моменты
Две хеш-функции: h1kkk = k % SIZE, h2kkk должно быть ненулевым и по возможности взаимно простым с SIZE. Часто берут h2kkk = 1 + kk % (SIZE-1)k при простом SIZE.Индекс для i-й попытки: idx = h1(k)+i∗h2(k)h1(k) + i * h2(k)h1(k)+i∗h2(k) % SIZE.Поиск: если попали в EMPTY — ключа нет, можно остановиться; если OCCUPIED и ключ совпал — найден; если DELETED или OCCUPIED с другим ключом — продолжаем.Вставка: при обходе запоминаем индекс первой DELETED ячейки; если находим EMPTY — вставляем либо в первую DELETED еслибылаесли былаеслибыла, либо в этот EMPTY; если найден совпадающий ключ — можно обновить или отказать.Удаление: найденную OCCUPIED ячейку помечаем DELETED.Пример кода наглядный,минимальныйнаглядный, минимальныйнаглядный,минимальный:
define SIZE 101 / лучше простое /define EMPTY 0define OCCUPIED 1define DELETED 2typedef struct {
int key;
int used; / EMPTY, OCCUPIED, DELETED /
} Entry;
Entry tableSIZESIZESIZE;
void init_tablevoidvoidvoid {
for (int i = 0; i < SIZE; ++i) tableiii.used = EMPTY;
}
static int h1intkeyint keyintkey {
return keykey % SIZE + SIZEkey % SIZE;
}
static int h2intkeyint keyintkey {
/ гарантируем ненулевой шаг, лучше если SIZE простое: 1 + key % SIZE−1SIZE-1SIZE−1 /
return 1 + (key(key % (SIZE - 1) + (SIZE - 1)) % (SIZE - 1)(key;
}
/ Поиск индекса ключа, возвращает индекс либо -1 если не найден /
int searchintkeyint keyintkey {
int start = h1keykeykey;
int step = h2keykeykey;
for (int i = 0, idx = start; i < SIZE; ++i, idx = (idx + step) % SIZE) {
if table[idx].used==EMPTYtable[idx].used == EMPTYtable[idx].used==EMPTY return -1; / дальше пусто — нет ключа /
if (table[idx].used == OCCUPIED && table[idx].key == key) return idx;
/ если DELETED или OCCUPIED другой ключ — продолжаем /
}
return -1;
}
/ Вставка: возвращает 0 — успешно, 1 — ключ уже есть, -1 — таблица полна /
int insertintkeyint keyintkey {
int start = h1keykeykey;
int step = h2keykeykey;
int first_deleted = -1;
for (int i = 0, idx = start; i < SIZE; ++i, idx = (idx + step) % SIZE) {
if table[idx].used==OCCUPIEDtable[idx].used == OCCUPIEDtable[idx].used==OCCUPIED {
if table[idx].key==keytable[idx].key == keytable[idx].key==key return 1; / уже есть /
} else if table[idx].used==DELETEDtable[idx].used == DELETEDtable[idx].used==DELETED {
if firstdeleted==−1first_deleted == -1firstd eleted==−1 first_deleted = idx;
} else { / EMPTY /
int pos = firstdeleted!=−1first_deleted != -1firstd eleted!=−1 ? first_deleted : idx;
tablepospospos.key = key;
tablepospospos.used = OCCUPIED;
return 0;
}
}
/ если цикл кончился, таблица либо заполнена OCCUPIED, либо только DELETED;
если был DELETED — вставляем туда /
if firstdeleted!=−1first_deleted != -1firstd eleted!=−1 {
tablefirstdeletedfirst_deletedfirstd eleted.key = key;
tablefirstdeletedfirst_deletedfirstd eleted.used = OCCUPIED;
return 0;
}
return -1; / полна /
}
/ Удаление: возвращает 0 — успешно, -1 — не найден /
int deleteintkeyint keyintkey {
int idx = searchkeykeykey;
if idx==−1idx == -1idx==−1 return -1;
tableidxidxidx.used = DELETED;
return 0;
}
Замечания и рекомендации
Лучше, чтобы SIZE было простым числом — тогда формула h2 = 1 + kk % (SIZE-1)k гарантирует, что шаг и SIZE взаимно просты и весь массив будет пройден.Если не хотите трёх состояний, можно хранить особое значение ключа как «пусто», но это неудобно и ненадёжно для общих ключей.При интенсивных операциях удаления может расти число DELETED ячеек → периодически полезно делать rehash пересоздатьтаблицуивставитьтолькоOCCUPIEDэлементыпересоздать таблицу и вставить только OCCUPIED элементыпересоздатьтаблицуивставитьтолькоOCCUPIEDэлементы.Для более качественного хеширования используйте хорошие хеш-функции для int например,перемешиваниебитовнапример, перемешивание битовнапример,перемешиваниебитов перед применением модификаторов.Если хотите, могу дать:
версию кода с обработкой ошибок и тестовым main;пример h2 для случая, когда SIZE — степень двойки;доказательство, почему нужно три состояния.