C ++中的map与hash_map


117

我在hash_mapmapC ++中有一个问题。我了解这map在STL中,但hash_map不是标准。两者有什么区别?

Answers:


133

它们以非常不同的方式实现。

hash_mapunordered_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存储到两个对象中的相同对象)中如果您使用的是实际实例)。然后,您将通过hash_map获得O(1)时间访问时间,并通过map获得O(n)迭代时间。当然,您必须记住每次都添加和删除指针。您可以轻松地编写一个自定义容器类(可能也将其模板化)来为您封装此行为。
雪碧2014年

2
@ErikGarrison当然,如果尝试这种方法,您将需要支付少量额外的空间。但是,由于您将使用指针,因此应该不要太多。如果确实愿意,可以精打细算,编写自己的AVL实现,并使用节点指针作为hash_map中的数据类型,这将使​​您有O(1)时间访问树中的节点的时间,您将可以线性迭代到所需的任何位置。当然,这将涉及大量的编码,并且我不确定它是否会得到回报,除非您需要在随机访问位置之间进行大量迭代。
雪碧2014年

35

hash_map是许多库实现提供的通用扩展。这就是将它重命名为unordered_mapTR1的一部分时将其重命名为C ++标准的原因。映射通常是通过平衡的二叉树(如红黑树)来实现的(实现方式当然会有所不同)。 hash_map并且unordered_map通常使用哈希表实现。因此,不维持顺序。 unordered_map插入/删除/查询将为O(1)(恒定时间),其中映射将为O(log n),其中n是数据结构中的项目数。因此unordered_map速度更快,如果您不在乎项目的顺序,则应该优先选择map。有时您想要保持顺序(按键排序),map因此可以选择。


9
我要指出的是,当可能发生冲突(错误的哈希fcn,加载因子太高等)时,hashmap具有最坏的O(N)访问权限
KitsuneYMG 2010年

一个好的哈希图的预期成本为O(1),但不能保证如此。错误的哈希图的预期成本可能不是O(1)。
2014年

14

一些关键差异在于复杂性要求。

  • 一个map需要O(log(N))时间的插入和认定操作,因为它是为实现红黑树的数据结构。

  • 一个unordered_map需要的“平均”时间O(1)插入和认定,但被允许具有的最坏情况的时间O(N)。这是因为它是使用哈希表数据结构实现的。

因此,通常unordered_map速度会更快,但是根据所存储的键和哈希函数,情况可能会变得更糟。


4

C ++规范没有确切说明必须为STL容器使用哪种算法。但是,它确实对它们的性能施加了某些限制,从而排除了对哈希表map和其他关联容器的使用。(它们通常用红色/黑色树来实现。)与散列表相比,这些约束要求这些容器具有更好的最坏情况性能。

但是,确实有很多人想要哈希表,因此多年来,基于哈希的STL关联容器一直是常见的扩展。因此,他们unordered_map在C ++标准的更高版本中添加了。


它实际上是在TR1(std :: tr1 :: unordered_map)中添加的,而不是C ++ 0x
Terry Mahaffey 2010年

我认为原因map通常是btree平衡,这是由于使用operator<()它来确定位置。
KitsuneYMG '02

@kts:是否有任何STL实现实际上使用B树?
bk1e 2010年

从技术上讲,所有二叉搜索树都是b树(1-2树)。话虽如此,我不知道任何使用红色或黑色以外的其他东西的STL
KitsuneYMG 2010年

@ bk1e“适当的” B树在数据库中非常有用,在该数据库中,您希望“胖”树节点与磁盘页面很好地对齐。OTOH,这在“正常”程序中使用的“扁平”内存模型中不是很有用-我知道的所有STL实现都使用红黑树。
Branko Dimitrijevic '02

3

mapbalanced binary search tree(通常是a rb_tree)实现,因为in中的所有成员balanced binary search tree都按map排序;

hash_map从中实现hashtable。由于in中的所有成员都未排序,hashtable因此in中的成员hash_map(unordered_map)未排序。

hash_map不是c ++标准库,但现在将其重命名为unordered_map(您可以认为它已重命名)并成为c ++标准库,因为c ++ 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_treehash_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个存储桶并插入一些值时,它是内部结构。

在此处输入图片说明

下图显示了map和hash_map(unordered_map)之间的一些区别,该图像来自于如何在map和unordered_map之间进行选择?

在此处输入图片说明


1

我不知道是什么,但是,hash_map需要20多秒钟才能清除()150K无符号整数键和浮点值。我只是在运行并阅读别人的代码。

这就是它包含hash_map的方式。

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

我在这里https://bytes.com/topic/c/answers/570079-perfomance-clear-vs-swap阅读

说clear()是O(N)的顺序。这对我来说很奇怪,但是事实就是这样。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.