Как бы вы реализовали хеш-таблицу на C++?

«Как бы вы реализовали хеш-таблицу на C++?» — вопрос из категории Алгоритмы и структуры данных, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Я бы реализовал хеш-таблицу с разрешением коллизий методом цепочек (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 или кастомный хешер.