C++ 中的 map 与 hash_map

Posted

技术标签:

【中文标题】C++ 中的 map 与 hash_map【英文标题】:map vs. hash_map in C++ 【发布时间】:2011-01-12 10:47:52 【问题描述】:

我对 C++ 中的 hash_mapmap 有疑问。我知道 map 在 STL 中,但 hash_map 不是标准。两者有什么区别?

【问题讨论】:

【参考方案1】:

C++ 规范没有明确说明您必须为 STL 容器使用什么算法。但是,它确实对它们的性能施加了某些限制,这就排除了将哈希表用于map 和其他关联容器的可能性。 (它们最常使用红/黑树来实现。)这些约束要求这些容器在最坏情况下的性能比哈希表所能提供的更好。

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

【讨论】:

其实是在TR1(std::tr1::unordered_map)中添加的,而不是C++0x 我认为map一般是平衡btree的原因是由于使用operator<()作为确定位置的手段。 @kts:任何 STL 实现实际上都使用 B 树吗? 技术上所有的二叉搜索树都是 b-trees(1-2 树)。话虽如此,我不知道有任何 STL 使用红/黑以外的任何东西 @bk1e “正确的” B 树在数据库中非常有用,您希望“胖”树节点与磁盘页面很好地对齐。 OTOH,这在“普通”程序中使用的“平面”内存模型中并不是那么有用——我所知道的所有 STL 实现都使用红黑树。【参考方案2】:

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

hash_map(TR1 和 Boost 中的unordered_map;改为使用这些)使用哈希表,其中键被散列到表中的一个槽,值存储在与该键相关的列表中。

map 实现为平衡二叉搜索树(通常是红/黑树)。

unordered_map 在访问集合的已知元素时应该会提供更好的性能,但map 将具有其他有用的特性(例如,它按排序顺序存储,允许从头到尾遍历)。 unordered_map 在插入和删除时会比 map 更快。

【讨论】:

我不完全同意你的表现。性能受许多参数的影响,我会责骂任何使用 unordered_map 仅 10 个条目的程序员,因为“它更快”。先关注界面/功能,再关注性能。 嗯,是的,如果您了解您的问题,它会有所帮助。在某些数量级上,这可能是一种清洗性能,但重要的是要了解这两个容器的性能特征,因为随着数据量变大,它们会以不同的方式出现偏差。 有趣的是,我只是在一个应用程序中用 boost::unordered_map 交换了一个 std::map,在这个应用程序中我做了很多随机查找,但也遍历了地图中的所有键。我在查找中节省了大量时间,但通过迭代获得了它,所以我切换回 map 并正在寻找其他方法来提高应用程序性能。 @ErikGarrison 如果您使用随机访问和迭代的次数远多于插入和删除元素,那么您可以将对象同时保存在树和 hash_map 中(通过存储指针,或者更好的是 shared_ptr , 到相同的对象,以防您使用实际实例)。然后,您将通过 hash_map 获得 O(1) 时间访问时间,并通过地图获得 O(n) 迭代时间。当然,您必须记住每次都添加和删除指针。您可以轻松编写一个自定义容器类(也可能是模板),为您封装此行为。 @ErikGarrison 当然,如果您尝试这种方法,您将支付少量额外空间。但是,由于您将使用指针,因此不应该太多。如果你真的想要,你可以过火,编写你自己的 AVL 实现,并在 hash_map 中使用节点指针作为你的数据类型,这将使​​你在 O(1) 时间内访问树中的一个节点您将能够线性迭代到您需要的任何地方。当然,这将涉及大量编码,我不确定它是否会得到回报,除非您需要在随机访问位置之间进行大量迭代。【参考方案3】:

hash_map 是许多库实现提供的通用扩展。这正是它在作为 TR1 的一部分添加到 C++ 标准时被重命名为 unordered_map 的原因。 map 通常使用平衡二叉树(如红黑树)来实现(当然实现方式会有所不同)。 hash_mapunordered_map 一般都是用哈希表实现的。因此不维持顺序。 unordered_map insert/delete/query 将为 O(1)(恒定时间),其中 map 将为 O(log n),其中 n 是数据结构中的项目数。所以unordered_map 更快,如果你不关心项目的顺序,应该优先于map。有时您想维持秩序(按密钥排序),为此map 将是选择。

【讨论】:

我会指出,当可能发生冲突时(错误的哈希 fcn、加载因子太高等),hashmap 的最坏情况访问为 O(N) 一个好的 hashmap 的预期成本为 O(1),但不能保证如此。糟糕的哈希图的预期成本可能不是 O(1)。【参考方案4】:

一些主要区别在于复杂性要求。

map 需要 O(log(N)) 时间进行插入和查找操作,因为它是作为 红黑树 数据结构实现的。

unordered_map 要求插入和查找的“平均”时间为 O(1),但允许最坏情况下的时间为 O(N)。这是因为它是使用 Hash Table 数据结构实现的。

因此,通常unordered_map 会更快,但取决于您存储的键和哈希函数,可能会变得更糟。

【讨论】:

【参考方案5】:

我不知道给出了什么,但是,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) 的顺序。对我来说,这很奇怪,但是,就是这样。

【讨论】:

【参考方案6】:

map 是从balanced binary search tree(通常是rb_tree)实现的,因为balanced binary search tree 中的所有成员都已排序,map 也是;

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

hash_map 不是 c++ 标准库,但现在它重命名为 unordered_map(你可以认为它已重命名)并成为 c++ 标准库,因为 c++11 看到这个问题Difference between hash_map and 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)的一些区别,图片来自How to choose between map and unordered_map?:

【讨论】:

以上是关于C++ 中的 map 与 hash_map的主要内容,如果未能解决你的问题,请参考以下文章

C ++中的map与hash_map

关联容器C++

关联容器C++

c++ map 的key可是是一个类吗

使用例子解释C++中的map容器

unordered_map 中的 C++ 线程(无复制构造函数)