Який найкращий спосіб використовувати HashMap в C ++?


174

Я знаю, що STL має API HashMap, але я не можу знайти жодної гарної та ретельної документації з хорошими прикладами щодо цього.

Будь-які добрі приклади будуть оцінені.


Ви питаєте про хеш-мапу C ++ 1x або про карту std ::?
благодійник

2
Я хочу щось на зразок java.util.HashMap в C ++ і стандартизований спосіб зробити це, якщо таке є. Інакше найкраща нестандартна бібліотека. Що зазвичай використовують розробники C ++, коли їм потрібен HashMap?
користувач855

Відповіді:


237

Стандартна бібліотека включає впорядковані та не упорядковані контейнери для карти ( 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
Ключ: привіт Значення: 23

Якщо вам потрібно замовити у вашому контейнері, і ви добре працюєте з O (log n), тоді просто використовуйте std::map.

В іншому випадку, якщо ви дійсно потребуєте хеш-таблицю (O (1) вставки / доступ), перевірте std::unordered_map, який має подібний до std::mapAPI (наприклад , в наведеному вище прикладі ви просто повинні знайти і замінити mapз unordered_map).

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

Ще до випуску C ++ 11 підтримується GCC unordered_map- у просторі імен std::tr1. Таким чином, для старих компіляторів GCC ви можете спробувати використовувати його так:

#include <tr1/unordered_map>

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

Він також є частиною прискорення, тобто ви можете використовувати відповідний заголовок підвищення для кращої портативності.


1
Хоча стандартна бібліотека не має контейнера хеш - таблицю на основі, майже всі реалізації включають в себе від SGI STL в тій чи іншій формі. hash_map
Джеймс Мак-Нілліс

@JamesMcNellis, який рекомендується unordered_map або hash_map для реалізації HashMap
Shameel Mohamed

2
@ShameelMohamed, 2017, тобто через 6 років після C ++ 11, важко знайти STL, який не надає unordered_map. Таким чином, немає підстав вважати нестандартним hash_map.
maxschlepzig

30

A hash_map- це старіша, нестандартна версія того, що для цілей стандартизації називається unordered_map(спочатку в TR1 і включене в стандарт з C ++ 11). Як випливає з назви, воно відрізняється std::mapголовним чином від того, що це не впорядковано - якщо, наприклад, ви повторюєте карту від begin()до end(), ви отримуєте елементи в порядку за ключем 1 , але якщо ви повторюєте через unordered_mapвід, begin()до end(), ви отримуєте елементи в більш-менш довільний порядок.

unordered_mapЗазвичай передбачається мати постійну складність. Тобто, вставка, пошук тощо, як правило, займає по суті фіксовану кількість часу, незалежно від кількості елементів у таблиці. Ця std::mapскладність є логарифмічною щодо кількості елементів, що зберігаються - це означає, що час вставлення або отримання елемента зростає, але досить повільно , оскільки карта збільшується. Наприклад, якщо для пошуку одного з 1 мільйона елементів потрібно 1 мікросекунда, то для пошуку одного з 2 мільйонів елементів потрібно 3 мікросекунди, 3 мікросекунди для одного з 4 мільйонів елементів, 4 мікросекунди для одного з 8 мільйонів предметів тощо.

З практичної точки зору, це насправді не вся історія. За своєю природою проста хеш-таблиця має фіксований розмір. Пристосування його до вимог змінних розмірів для контейнера загального призначення дещо нетривіально. Як результат, операції, які (потенційно) збільшують таблицю (наприклад, вставка), потенційно відносно повільні (тобто більшість є досить швидкими, але періодично одна буде набагато повільнішою). Пошук, який не може змінити розмір таблиці, як правило, набагато швидший. Як результат, більшість таблиць на основі хешів, як правило, найкращі, коли ви робите велику кількість оглядів, порівняно з кількістю вставок. У випадках, коли ви вставляєте багато даних, потім повторіть таблицю один раз, щоб отримати результати (наприклад, підрахунок кількості унікальних слів у файлі), ймовірно, щоstd::map буде настільки ж швидким і цілком можливо навіть швидшим (але, знову ж таки, складність обчислювань різна, так що також може залежати від кількості унікальних слів у файлі).


1 Якщо замовлення визначено третім параметром шаблону під час створення карти std::less<T>за замовчуванням.


1
Я розумію, що я приходжу через 9 років після опублікування відповіді, але ... чи є у вас посилання на документ, який згадує про те, що не упорядкована карта може зменшитись у розмірі? Зазвичай std-колекції тільки ростуть. Крім того, якщо ви вставляєте багато даних, але заздалегідь знаєте більше або менше, скільки клавіш будете вставляти, ви можете вказати розмір карти при створенні, що в основному анулює вартість зміни розміру (тому що не буде жодної) .
Зонко

@Zonko: Вибачте, я не помітив цього, коли мене запитали. Наскільки я знаю, не упорядкована_мапа не зменшується, за винятком відповіді на дзвінки rehash. Коли ви телефонуєте rehash, ви вказуєте розмір таблиці. Цей розмір буде використовуватися, якщо це не перевищить вказаний максимальний коефіцієнт навантаження для таблиці (у цьому випадку розмір буде автоматично збільшуватися, щоб утримувати коефіцієнт навантаження в межах).
Джеррі Труну

22

Ось більш повний і гнучкий приклад, який не опускає необхідних включень для створення помилок компіляції:

#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
Треба сказати, цей приклад є дуже поганою практикою в C ++. Ви використовуєте сильно набрану мову та знищуєте її за допомогою void*. Для початку, немає ніяких причин обгортати те unordered_map, що воно є частиною стандарту, і зменшує можливість ремонту коду. Далі, якщо наполягаєте на обгортанні, використовуйте templates. Саме для цього вони і є.
гуярад

Сильно набрали? Ви, мабуть, маєте на увазі статично набрані. Те, що він може перейти від const char ptr до недійсності, мовчить робить C ++ статично, але не сильно, набраним. Існують типи, але компілятор нічого не скаже, якщо ви не включите якийсь незрозумілий прапор, який, швидше за все, не існує.
Sahsahae

6

Докази, що std::unordered_mapвикористовують хеш-карту в GCC stdlibc ++ 6.4

Про це згадувалося на веб- сайті: https://stackoverflow.com/a/3578247/895245, але у такій відповіді: Яка структура даних всередині std :: map в C ++? Я надав додаткові докази таких дій для реалізації GCC stdlibc ++ 6.4 шляхом:

  • Крок налагодження GDB у класі
  • аналіз характеристик продуктивності

Ось попередній графік характеристик ефективності, описаний у цій відповіді:

введіть тут опис зображення

Як використовувати користувацький клас та хеш-функцію за допомогою unordered_map

Ця відповідь зазначає: C ++ unororder_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);
    }
  };

}

0

Для тих із нас, хто намагається розібратися, як хешировать власні класи, використовуючи стандартний шаблон, є просте рішення:

  1. У вашому класі потрібно визначити перевантаження оператора рівності ==. Якщо ви не знаєте, як це зробити, у GeeksforGeeks є чудовий підручник https://www.geeksforgeeks.org/operator-overloading-c/

  2. У стандартному просторі імен оголосіть структуру шаблону під назвою хеш із назвою вашого класу як тип (див. Нижче). Я знайшов чудовий поштовий блог, який також показує приклад обчислення хешів за допомогою XOR та бітшифтінга, але це поза межами цього питання, але воно також містить детальні інструкції щодо виконання хеш-функцій https://prateekvjoshi.com/ 2014/06/05 / за допомогою класів-хеш-функцій-в-с-для визначених користувачем класів /

namespace std {

  template<>
  struct hash<my_type> {
    size_t operator()(const my_type& k) {
      // Do your hash function here
      ...
    }
  };

}
  1. Тоді для того, щоб реалізувати хештель за допомогою нової хеш-функції, вам просто потрібно створити std::mapабо std::unordered_mapтак, як ви зазвичай робите і використовувати my_typeв якості ключа, стандартна бібліотека автоматично використовувати хеш-функцію, яку ви визначили раніше (на кроці 2) ваші ключі.
#include <unordered_map>

int main() {
  std::unordered_map<my_type, other_type> my_map;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.