Ответ
Я бы реализовал хеш-таблицу с разрешением коллизий методом цепочек (separate chaining). Вот базовая структура и ключевые методы:
template<typename Key, typename Value, typename Hash = std::hash<Key>>
class HashTable {
private:
std::vector<std::list<std::pair<Key, Value>>> buckets;
Hash hasher;
size_t itemCount = 0;
const double maxLoadFactor = 0.75;
size_t getBucketIndex(const Key& key) const {
return hasher(key) % buckets.size();
}
public:
HashTable(size_t initialSize = 16) : buckets(initialSize) {}
void insert(const Key& key, Value value) {
// Проверка необходимости рехеширования
if (load_factor() > maxLoadFactor) {
rehash(buckets.size() * 2);
}
size_t index = getBucketIndex(key);
auto& bucket = buckets[index];
// Проверка, существует ли уже ключ
for (auto& pair : bucket) {
if (pair.first == key) {
pair.second = std::move(value); // Обновление значения
return;
}
}
// Вставка новой пары
bucket.emplace_back(key, std::move(value));
++itemCount;
}
Value* find(const Key& key) {
size_t index = getBucketIndex(key);
for (auto& pair : buckets[index]) {
if (pair.first == key) {
return &pair.second;
}
}
return nullptr;
}
void rehash(size_t newBucketCount) {
std::vector<std::list<std::pair<Key, Value>>> newBuckets(newBucketCount);
for (auto& bucket : buckets) {
for (auto& pair : bucket) {
size_t newIndex = hasher(pair.first) % newBucketCount;
newBuckets[newIndex].push_back(std::move(pair));
}
}
buckets = std::move(newBuckets);
}
double load_factor() const {
return static_cast<double>(itemCount) / buckets.size();
}
};
Объяснение выбора подхода:
- Метод цепочек проще для реализации и надежнее при плохой хеш-функции.
std::listдля бакета — классический выбор, но в высокопроизводительных сценариях можно использоватьstd::vectorили односвязный список.- Рехеширование критически важно для поддержания производительности (среднее время поиска O(1)).
- Шаблоны позволяют использовать таблицу с любыми типами, поддерживающими
std::hashили кастомный хешер.