Каков наилучший способ использования HashMap в С++?

Я знаю, что у STL есть API HashMap, но я не могу найти хорошую и полную документацию с хорошими примерами относительно этого.

Любые хорошие примеры будут оценены.

Ответ 1

Стандартная библиотека включает контейнеры упорядоченных и неупорядоченных карт ( std::map и std::unordered_map). В упорядоченной карте элементы сортируются по ключу, вставка и доступ осуществляется в O (log n). Обычно стандартная библиотека внутренне использует красные черные деревья для упорядоченных карт. Но это только деталь реализации. В неупорядоченную карту вставьте и получите доступ в O (1). Это просто другое название хеш-таблицы.

Пример с (упорядоченным) std::map:

#include <map>
#include <iostream>
#include <cassert>

int main(int argc, char **argv)
{
  std::map<std::string, int> m;
  m["hello"] = 23;
  // check if key is present
  if (m.find("world") != m.end())
    std::cout << "map contains key world!\n";
  // retrieve
  std::cout << m["hello"] << '\n';
  std::map<std::string, int>::iterator i = m.find("hello");
  assert(i != m.end());
  std::cout << "Key: " << i->first << " Value: " << i->second << '\n';
  return 0;
}

Выход:

23
Key: hello Value: 23

Если вам нужен порядок в вашем контейнере и вы согласны со временем выполнения O (log n), просто используйте std::map.

В противном случае, если вам действительно нужна хеш-таблица (O (1) insert/access), проверьте std::unordered_map, который имеет аналог API std::map (например, в приведенном выше примере вам просто нужно найти и заменить map с unordered_map).

Контейнер unordered_map был представлен в стандартной версии С++ 11. Таким образом, в зависимости от вашего компилятора, вы должны включить функции С++ 11 (например, при использовании GCC 4.8 вы должны добавить -std=c++11 в CXXFLAGS).

Еще до выпуска С++ 11 GCC поддерживал unordered_map - в пространстве имен std::tr1. Таким образом, для старых компиляторов GCC вы можете попробовать использовать его следующим образом:

#include <tr1/unordered_map>

std::tr1::unordered_map<std::string, int> m;

Это также является частью boost, т.е. вы можете использовать соответствующий boost-header для лучшей переносимости.

Ответ 2

hash_map - это старая, нестандартная версия того, что для целей стандартизации называется unordered_map (изначально в TR1 и включено в стандарт начиная с С++ 11). Как видно из названия, оно отличается от std::map основном неупорядоченным - если, например, вы перебираете карту от begin() до end(), вы получаете элементы в порядке по ключу 1 но если вы перебираете через unordered_map от begin() до end() вы получаете элементы в более или менее произвольном порядке.

Обычно ожидается, что unordered_map будет иметь постоянную сложность. То есть вставка, поиск и т.д. Обычно занимают фиксированное количество времени, независимо от того, сколько элементов в таблице. std::map имеет сложность, которая логарифмируется по количеству сохраняемых элементов - что означает, что время для вставки или извлечения элемента увеличивается, но довольно медленно, по мере того, как карта увеличивается. Например, если для поиска одного из 1 миллиона элементов требуется 1 микросекунда, то можно ожидать, что для поиска одного из 2 миллионов элементов потребуется около 2 микросекунд, 3 микросекунды для одного из 4 миллионов элементов, 4 микросекунды для одного из 8 миллионов. предметы и т.д.

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


1 Когда порядок определяется третьим параметром шаблона при создании карты, std::less<T> по умолчанию.

Ответ 3

Здесь более полный и гибкий пример, который не содержит необходимости, включает в себя создание ошибок компиляции:

#include <iostream>
#include <unordered_map>

class Hashtable {
    std::unordered_map<const void *, const void *> htmap;

public:
    void put(const void *key, const void *value) {
            htmap[key] = value;
    }

    const void *get(const void *key) {
            return htmap[key];
    }

};

int main() {
    Hashtable ht;
    ht.put("Bob", "Dylan");
    int one = 1;
    ht.put("one", &one);
    std::cout << (char *)ht.get("Bob") << "; " << *(int *)ht.get("one");
}

Все еще не особенно полезно для ключей, если они не предопределены как указатели, потому что соответствующее значение не будет выполнено! (Тем не менее, поскольку я обычно использую строки для ключей, подстановка "string" для "const void *" в объявлении ключа должна решить эту проблему.)

Ответ 4

Доказательство того, что std::unordered_map использует хэш-карту в GCC stdlibc++ 6.4

Об этом говорилось по адресу: fooobar.com/info/44832/... но в следующем ответе: Какая структура данных находится внутри std :: map в C++? Я предоставил дополнительные доказательства такого применения GCC stdlibc++ 6.4:

  • GDB пошаговая отладка в классе
  • анализ характеристик

Вот предварительный просмотр графика характеристик производительности, описанного в этом ответе:

enter image description here

Как использовать пользовательский класс и хэш-функцию с unordered_map

Этот ответ гласит: C++ unordered_map с использованием пользовательского типа класса в качестве ключа

Выдержка: равенство:

struct Key
{
  std::string first;
  std::string second;
  int         third;

  bool operator==(const Key &other) const
  { return (first == other.first
            && second == other.second
            && third == other.third);
  }
};

Хэш-функция:

namespace std {

  template <>
  struct hash<Key>
  {
    std::size_t operator()(const Key& k) const
    {
      using std::size_t;
      using std::hash;
      using std::string;

      // Compute individual hash values for first,
      // second and third and combine them using XOR
      // and bit shifting:

      return ((hash<string>()(k.first)
               ^ (hash<string>()(k.second) << 1)) >> 1)
               ^ (hash<int>()(k.third) << 1);
    }
  };

}