← Назад к вопросам
Приведи пример хеш функции для int
2.0 Middle🔥 151 комментариев
#Структуры данных и алгоритмы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Примеры хеш-функций для int
Хеш-функция преобразует входное значение в индекс для хеш-таблицы. Для целых чисел есть несколько подходов разной сложности.
1. Простая и наивная хеш-функция
Самый простой вариант — просто вернуть само число:
#include <cstdint>
uint32_t hash_simple(int key) {
return static_cast<uint32_t>(key);
}
// Использование:
int table_size = 1000;
int index = hash_simple(42) % table_size; // 42 % 1000 = 42
Проблемы:
- Плохое распределение: соседние числа дают соседние хеши
- Для таблицы размером N первые N чисел конфликтуют минимально
- Может привести к кластеризации (clustering) в хеш-таблице
2. Разумная хеш-функция (Division-based)
Использование простого числа в качестве размера таблицы:
#include <cstdint>
#include <cstdlib>
uint32_t hash_division(int key, int table_size) {
// Берём остаток от деления
// table_size должно быть простым числом для лучшего распределения
return static_cast<uint32_t>(std::abs(key)) % table_size;
}
// Использование:
int table_size = 101; // Простое число
int index = hash_division(42, table_size);
int index2 = hash_division(-42, table_size); // Также 42
Преимущества:
- Просто и понятно
- Работает лучше, если table_size — простое число
- Хорошо для ненегативных чисел
Недостатки:
- Всё ещё плохое распределение для некоторых последовательностей
- Операция modulo медленнее, чем битовые операции
3. Битовая операция (для размеров, кратных степени 2)
#include <cstdint>
uint32_t hash_bitwise(int key) {
// Берём логику: используем нижние биты
// Это быстрее modulo
return static_cast<uint32_t>(key) & 0xFF; // 256 вариантов
}
// Или для произвольного размера, кратного 2^k:
uint32_t hash_power_of_two(int key, int mask) {
// mask = table_size - 1, где table_size = 2^k
return static_cast<uint32_t>(key) & mask;
}
// Использование:
int mask = 127; // для таблицы размером 128 (2^7)
int index = hash_power_of_two(42, mask);
Преимущества:
- Очень быстро (битовая операция вместо деления)
- Идеален для power-of-2 размеров таблиц
Недостатки:
- Плохое распределение нижних битов
- Требует size = 2^k
4. Multiplicative hashing (Умножающее хеширование)
Использует золотое сечение для лучшего распределения:
#include <cstdint>
uint32_t hash_multiplicative(int key, int table_size) {
// Константа золотого сечения для 32-бит: 2654435761
// Это (sqrt(5) - 1) / 2 * 2^32 ≈ 0.618... * 2^32
const uint32_t GOLDEN = 2654435761U;
uint32_t h = static_cast<uint32_t>(key) * GOLDEN;
return (h >> 16) % table_size; // Берём верхние биты
}
// Использование:
int index = hash_multiplicative(42, 1000);
Преимущества:
- Хорошее распределение даже для последовательных чисел
- Быстро для больших таблиц
- Математически обоснованный подход
Недостатки:
- Сложнее для понимания
- Немного медленнее, чем простое modulo
5. Java-style хеш (распространённый подход)
#include <cstdint>
uint32_t hash_java_style(int key) {
// Используется в Java HashMap
uint32_t h = static_cast<uint32_t>(key);
h ^= (h >> 16); // XOR с битами, сдвинутыми на 16
return h;
}
// Или с модификациями:
uint32_t hash_java_style_v2(int key, int table_size) {
uint32_t h = static_cast<uint32_t>(key);
h ^= (h >> 16);
h ^= (h >> 8); // Дополнительное перемешивание
return h % table_size;
}
int index = hash_java_style_v2(42, 1000);
Преимущества:
- Хорошее распределение
- Проверено годами в production коде
- Простая реализация
6. FNV-1a хеш (криптографический подход)
Это более серьёзный хеш, используется для хеширования строк и данных:
#include <cstdint>
uint32_t hash_fnv1a(const uint8_t* data, size_t len) {
const uint32_t FNV_prime = 16777619U; // 2^24 + 2^8 + 0x93
const uint32_t offset_basis = 2166136261U;
uint32_t hash = offset_basis;
for (size_t i = 0; i < len; i++) {
hash ^= data[i];
hash *= FNV_prime;
}
return hash;
}
// Для int:
uint32_t hash_fnv1a_int(int key) {
union {
int i;
uint8_t bytes[sizeof(int)];
} u = {key};
return hash_fnv1a(u.bytes, sizeof(int));
}
int index = hash_fnv1a_int(42) % 1000;
Преимущества:
- Отличное распределение
- Устойчив к коллизиям
- Используется профессионально
Недостатки:
- Медленнее для простых типов
- Избыточен для простых чисел
7. Практический пример: хеш-таблица
#include <vector>
#include <cstdint>
#include <iostream>
template <typename T>
class SimpleHashTable {
private:
static const int TABLE_SIZE = 101; // Простое число
std::vector<T> table{TABLE_SIZE};
uint32_t hash(int key) const {
// Используем multiplicative хеширование
const uint32_t GOLDEN = 2654435761U;
uint32_t h = static_cast<uint32_t>(key) * GOLDEN;
return (h >> 16) % TABLE_SIZE;
}
public:
void insert(int key, const T& value) {
int index = hash(key);
table[index] = value;
}
T* find(int key) {
int index = hash(key);
return &table[index];
}
};
int main() {
SimpleHashTable<int> ht;
ht.insert(42, 100);
ht.insert(123, 200);
auto val = ht.find(42);
std::cout << "Значение: " << *val << std::endl;
return 0;
}
Рекомендации по выбору
Используй:
- Простую модульную (division) — для начальных проектов, небольших таблиц
- Multiplicative — для production кода с большими таблицами
- FNV-1a — если нужна надёжность и хорошее распределение
- Битовую операцию — если size = 2^k и нужна максимальная скорость
Ключевые свойства хорошей хеш-функции:
- Детерминированность (одному ключу — один хеш)
- Равномерное распределение
- Минимум коллизий
- Быстрое вычисление
- Чувствительность к изменениям входных данных
В C++11+ лучше использовать std::hash:
#include <functional>
#include <unordered_map>
std::unordered_map<int, int> map; // Использует std::hash<int>
map[42] = 100;