map против hash_map в C ++

117

У меня вопрос hash_mapи mapпо C ++. Я понимаю, что mapэто в STL, но hash_mapэто не стандарт. В чем разница между ними?

skydoor
источник

Ответы:

133

Реализованы они очень по-разному.

hash_map( unordered_mapв TR1 и Boost; используйте их вместо) используйте хеш-таблицу, в которой ключ хешируется в слот в таблице, а значение сохраняется в списке, привязанном к этому ключу.

map реализован как сбалансированное двоичное дерево поиска (обычно красное / черное дерево).

unordered_mapДолжны дать немного более высокую производительность для доступа к известным элементам коллекции, но mapбудет иметь дополнительные полезные свойства (например , они хранятся в отсортированном порядке, что позволяет обход от начала до конца). unordered_mapбудет быстрее вставлять и удалять, чем map.

Джо
источник
7
Я не полностью согласен с вами относительно производительности. На производительность влияет ряд параметров, и я бы ругал любого программиста, использующего unordered_map всего за 10 записей, потому что «это быстрее». В первую очередь беспокойтесь об интерфейсе / функциональности, а потом о производительности.
Matthieu M.
24
Что ж, да, это помогает, если вы понимаете свою проблему. До определенных порядков это, вероятно, связано с производительностью стирки, но важно понимать характеристики производительности обоих контейнеров, поскольку они по-разному отличаются по мере увеличения объемов данных.
Джо
7
Интересно, что я просто заменил std :: map на boost :: unordered_map в приложении, в котором я выполняю много случайных поисков, но также перебираю все ключи на карте. Я сэкономил много времени на поиске, но вернул его с помощью итераций, поэтому я снова переключился на карту и ищу другие способы улучшить производительность приложения.
Эрик Гаррисон,
4
@ErikGarrison Если вы используете произвольный доступ и итерацию намного чаще, чем вставляете и удаляете элементы, вы можете иметь свои объекты как в дереве, так и в hash_map (сохраняя указатель или, еще лучше, shared_ptr, на одни и те же объекты в обоих в если вы использовали реальные экземпляры). Тогда у вас будет время доступа O (1) через hash_map и время итерации O (n) через карту. Конечно, вы должны не забывать добавлять и удалять указатели каждый раз. Вы можете легко написать собственный класс контейнера, который (возможно, также шаблон), который инкапсулирует это поведение за вас.
спрайт
2
@ErikGarrison Конечно, если вы попробуете этот метод, вам придется заплатить небольшим дополнительным пространством. Однако, поскольку вы будете использовать указатели, этого не должно быть слишком много. Если вы действительно хотите, вы можете пойти за борт и написать свою собственную реализацию AVL и использовать указатель узла в качестве типа данных в hash_map, это даст вам доступ времени O (1) к узлу в дереве, из которого вы сможете линейно выполнять итерацию туда, где вам нужно. Конечно, это потребует довольно много кода, и я не уверен, что это окупится, если вам не нужно много повторять из мест произвольного доступа и обратно.
спрайт
35

hash_mapбыло распространенным расширением, предоставляемым многими реализациями библиотек. Именно поэтому его переименовали в, unordered_mapкогда он был добавлен в стандарт C ++ как часть TR1. map обычно реализуется с помощью сбалансированного двоичного дерева, такого как красно-черное дерево (реализации, конечно, различаются). hash_mapи unordered_mapобычно реализуются с помощью хеш-таблиц. Таким образом, порядок не поддерживается. unordered_mapinsert / delete / query будет O (1) (постоянное время), где map будет O (log n), где n - количество элементов в структуре данных. Так unordered_mapработает быстрее, и если вам не важен порядок элементов, то предпочтение следует отдавать map. Иногда вы хотите поддерживать порядок (упорядоченный по ключу), и это mapбудет выбор.

janglin
источник
9
Я хотел бы указать, что hashmap имеет доступ O (N) для худшего случая, когда вероятны коллизии (плохой хэш fcn, слишком высокий коэффициент загрузки и т. Д.)
KitsuneYMG
Хорошая хэш-карта имеет ожидаемую стоимость O (1), что не гарантируется. Плохие хэш-карты могут иметь ожидаемую стоимость, отличную от O (1).
Четче
14

Некоторые из ключевых отличий заключаются в требованиях к сложности.

  • A mapтребует O(log(N))времени для операций вставки и поиска, поскольку он реализован в виде структуры данных Red-Black Tree .

  • Для вставки и поиска unordered_mapтребуется «среднее» время O(1), но разрешено иметь наихудшее время O(N). Это потому, что он реализован с использованием структуры данных Hash Table .

Обычно unordered_mapэто будет быстрее, но в зависимости от ключей и хэш-функции, которые вы храните, может стать намного хуже.

R Самуэль Клатчко
источник
4

Спецификация C ++ не говорит точно, какой алгоритм вы должны использовать для контейнеров STL. Однако это накладывает определенные ограничения на их производительность, что исключает использование хеш-таблиц дляmap и других ассоциативных контейнеров. (Чаще всего они реализуются с помощью красно-черных деревьев.) Эти ограничения требуют для этих контейнеров более высокой производительности в худшем случае, чем могут обеспечить хэш-таблицы.

Однако многим людям действительно нужны хеш-таблицы, поэтому ассоциативные контейнеры STL на основе хешей уже много лет являются обычным расширением. Следовательно, они добавили unordered_mapи тому подобное в более поздние версии стандарта C ++.

Уоррен Янг
источник
Фактически он был добавлен в TR1 (std :: tr1 :: unordered_map), а не в C ++ 0x
Терри Махаффи
Я думал, что причина в mapтом, что обычно сбалансированное btree было связано с использованием operator<()в качестве средства определения местоположения.
KitsuneYMG 03
@kts: Действительно ли какие-либо реализации STL используют B-дерево?
bk1e 03
Технически все деревья двоичного поиска являются b-деревьями (дерево 1-2). При этом я не знаю ни одного STL, который использует что-либо, кроме красного / черного
KitsuneYMG
@ bk1e «Правильные» B-деревья исключительно полезны в базах данных, где вам нужны «толстые» узлы дерева, которые хорошо согласовываются со страницами диска. OTOH, это не так полезно в «плоской» модели памяти, используемой в «обычных» программах - все известные мне реализации STL используют красно-черные деревья.
Бранко Димитриевич
3

mapреализуется из balanced binary search tree(обычно a rb_tree), поскольку все члены balanced binary search treeсортируются так, как map;

hash_mapреализуется из hashtable.S Поскольку все hashtableчлены в hash_map(unordered_map)не отсортированы, поэтому члены в не сортируются.

hash_mapне является стандартной библиотекой С ++, но теперь она переименована в unordered_map(вы можете подумать о ее переименовании) и становится стандартной библиотекой С ++, так как С ++ 11 см. этот вопрос Разница между hash_map и unordered_map?для более подробной информации.

Ниже я приведу базовый интерфейс из исходного кода, показывающий, как реализована карта двух типов.

карта:

Приведенный ниже код просто показывает, что map - это просто оболочка balanced binary search tree, почти вся ее функция - это просто вызвать balanced binary search treeфункцию.

template <typename Key, typename Value, class Compare = std::less<Key>>
class map{
    // used for rb_tree to sort
    typedef Key    key_type;

    // rb_tree node value
    typedef std::pair<key_type, value_type> value_type;

    typedef Compare key_compare;

    // as to map, Key is used for sort, Value used for store value
    typedef rb_tree<key_type, value_type, key_compare> rep_type;

    // the only member value of map (it's  rb_tree)
    rep_type t;
};

// one construct function
template<typename InputIterator>
map(InputIterator first, InputIterator last):t(Compare()){
        // use rb_tree to insert value(just insert unique value)
        t.insert_unique(first, last);
}

// insert function, just use tb_tree insert_unique function
//and only insert unique value
//rb_tree insertion time is : log(n)+rebalance
// so map's  insertion time is also : log(n)+rebalance 
typedef typename rep_type::const_iterator iterator;
std::pair<iterator, bool> insert(const value_type& v){
    return t.insert_unique(v);
};

hash_map:

hash_mapреализован hashtable, структура которого выглядит примерно так:

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

В приведенном ниже коде я приведу основную часть hashtable, а затем дам hash_map.

// used for node list
template<typename T>
struct __hashtable_node{
    T val;
    __hashtable_node* next;
};

template<typename Key, typename Value, typename HashFun>
class hashtable{
    public:
        typedef size_t   size_type;
        typedef HashFun  hasher;
        typedef Value    value_type;
        typedef Key      key_type;
    public:
        typedef __hashtable_node<value_type> node;

        // member data is buckets array(node* array)
        std::vector<node*> buckets;
        size_type num_elements;

        public:
            // insert only unique value
            std::pair<iterator, bool> insert_unique(const value_type& obj);

};

Как map'sтолько член rb_tree, hash_map'sединственный член hashtable. Это основной код, как показано ниже:

template<typename Key, typename Value, class HashFun = std::hash<Key>>
class hash_map{
    private:
        typedef hashtable<Key, Value, HashFun> ht;

        // member data is hash_table
        ht rep;

    public:
        // 100 buckets by default
        // it may not be 100(in this just for simplify)
        hash_map():rep(100){};

        // like the above map's insert function just invoke rb_tree unique function
        // hash_map, insert function just invoke hashtable's unique insert function
        std::pair<iterator, bool> insert(const Value& v){
                return t.insert_unique(v);
        };

};

На изображении ниже показано, когда hash_map имеет 53 сегмента и вставляет некоторые значения, это внутренняя структура.

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

На изображении ниже показана некоторая разница между картой и hash_map (unordered_map), изображение взято из Как выбрать между картой и unordered_map? :

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

Jayhello
источник
1

Я не знаю, что дает, но hash_map требуется более 20 секунд для clear () 150K целочисленных ключей без знака и значений с плавающей запятой. Я просто бегаю и читаю чужой код.

Вот как он включает hash_map.

#include "StdAfx.h"
#include <hash_map>

Я прочитал это здесь https://bytes.com/topic/c/answers/570079-perfomance-clear-vs-swap

говоря, что clear () является порядком O (N). Для меня это очень странно, но так оно и есть.

BoBoDev
источник