【问题标题】:What is the best way to use a HashMap in C++?在 C++ 中使用 HashMap 的最佳方法是什么?
【发布时间】:2011-04-04 10:23:51
【问题描述】:

我知道 STL 有一个 HashMap API,但我找不到任何好的和详尽的文档以及关于这方面的好例子。

任何好的例子都会受到赞赏。

【问题讨论】:

  • 你问的是 C++1x hash_map 还是 std::map ?
  • 我想要 C++ 中的 java.util.HashMap 之类的东西,如果有的话,还有标准化的方法。其他最好的非标准库。 C++ 开发人员在需要 HashMap 时通常使用什么?

标签: c++ hashmap


【解决方案1】:

对于我们这些试图弄清楚如何在仍然使用标准模板的同时对自己的类进行哈希处理的人来说,有一个简单的解决方案:

  1. 在您的类中,您需要定义一个相等运算符重载==。如果你不知道怎么做,GeeksforGeeks 有一个很棒的教程https://www.geeksforgeeks.org/operator-overloading-c/

  2. 在标准命名空间下,声明一个名为 hash 的模板结构,并将您的类名作为类型(见下文)。我发现了一篇很棒的博文,其中还展示了使用 XOR 和位移位计算哈希的示例,但这超出了本问题的范围,但它还包含有关如何使用哈希函数完成的详细说明https://prateekvjoshi.com/2014/06/05/using-hash-function-in-c-for-user-defined-classes/

namespace std {

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

}
  1. 那么要使用你的新哈希函数实现一个哈希表,你只需要像平常一样创建一个std::mapstd::unordered_map并使用my_type作为键,标准库将自动使用您之前(在第 2 步中)定义的散列函数来散列您的密钥。
#include <unordered_map>

int main() {
  std::unordered_map<my_type, other_type> my_map;
}

【讨论】:

    【解决方案2】:

    std::unordered_map 在 GCC stdlibc++ 6.4 中使用哈希映射的证据

    这在:https://stackoverflow.com/a/3578247/895245 中提到,但在以下答案中:What data structure is inside std::map in C++? 我已经通过以下方式为 GCC stdlibc++ 6.4 实现提供了进一步的证据:

    • GDB 单步调试进入类
    • 性能特征分析

    这是该答案中描述的性能特征图的预览:

    如何在unordered_map 中使用自定义类和哈希函数

    这个答案很准确:C++ unordered_map using a custom class type as the key

    摘录:平等:

    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);
        }
      };
    
    }
    

    【讨论】:

      【解决方案3】:

      hash_map 是一个较旧的非标准化版本,用于标准化目的称为 unordered_map(最初在 TR1 中,自 C++11 起包含在标准中)。顾名思义,它与std::map 的不同之处主要在于它是无序的——例如,如果您遍历从begin()end() 的映射,则可以通过key1按顺序获取项目>,但是如果您从begin()end() 遍历unordered_map,您将获得或多或少任意顺序的项目。

      unordered_map 通常具有恒定的复杂性。也就是说,无论表中有多少项目,插入、查找等通常基本上需要固定的时间。 std::map 的复杂性与存储的项目数量成对数 - 这意味着插入或检索项目的时间会增加,但随着地图变大,会非常缓慢。例如,如果查找 100 万个项目中的一项需要 1 微秒,那么您可以预期查找 200 万项中的一项需要大约 2 微秒,400 万项中的一项需要 3 微秒,800 万项中的一项需要 4 微秒物品等

      从实际的角度来看,这并不是故事的全部。本质上,简单的哈希表具有固定的大小。使其适应通用容器的可变大小要求有些重要。因此,(可能)增长表的操作(例如,插入)可能相对较慢(也就是说,大多数操作相当快,但周期性地会慢得多)。不能改变表大小的查找通常要快得多。因此,与插入次数相比,当您进行大量查找时,大多数基于哈希的表往往处于最佳状态。对于插入大量数据的情况,然后遍历表一次以检索结果(例如,计算文件中唯一单词的数量),std::map 的速度可能会一样快,甚至可能更快(但同样,计算复杂度不同,因此也可能取决于文件中唯一单词的数量)。


      1 其中顺序由创建地图时的第三个模板参数定义,默认为std::less&lt;T&gt;

      【讨论】:

      • 我意识到我在答案发布 9 年后才来,但是......你有一个文档的链接,提到一个无序地图可以缩小大小的事实吗?通常,std 集合只会增长。此外,如果您插入大量数据但提前或多或少知道要插入多少个键,您可以在创建时指定映射的大小,这基本上可以抵消调整大小的成本(因为不会有任何) .
      • @Zonko:对不起,当被问到时我没有注意到这一点。据我所知,unordered_map 不会缩小,除非响应调用rehash。当您调用rehash 时,您指定了表格的大小。除非这样做会超过为表指定的最大负载因子,否则将使用该大小(在这种情况下,大小将自动增加以将负载因子保持在限制范围内)。
      【解决方案4】:

      标准库包括有序和无序映射(std::mapstd::unordered_map)容器。在有序映射中,元素按键排序,插入和访问位于O(log n)。通常标准库内部使用red black trees 用于有序映射。但这只是一个实现细节。在无序映射中,插入和访问在 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::map API 类似(例如,在上面的示例中,您只需搜索并将map 替换为unordered_map)。

      unordered_map 容器是在 C++11 standard 修订版中引入的。因此,根据您的编译器,您必须启用 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;
      

      它也是boost的一部分,即您可以使用相应的boost-header以获得更好的便携性。

      【讨论】:

      • 虽然 the 标准库没有基于哈希表的容器,但几乎所有实现都以某种形式包含来自 SGI STL 的hash_map
      • @JamesMcNellis 建议使用 unordered_map 或 hash_map 来实现 HashMap
      • @ShameelMohamed,2017 年,即 C++11 之后的 6 年,应该很难找到不提供 unordered_map 的 STL。因此,没有理由考虑非标准的hash_map
      【解决方案5】:

      这是一个更完整、更灵活的示例,它没有省略生成编译错误所必需的包含:

      #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");
      }
      

      对于键仍然不是特别有用,除非它们被预定义为指针,因为匹配的值是不行的! (但是,由于我通常使用字符串作为键,因此在键的声明中将“字符串”替换为“const void *”应该可以解决此问题。)

      【讨论】:

      • 我不得不说,这个例子在 C++ 中是一个非常糟糕的实践。您正在使用强类型语言并使用void* 破坏它。对于初学者来说,没有理由包装unordered_map,因为它是标准的一部分并降低了代码的可维护性。接下来,如果坚持包装它,请使用templates。这正是他们的目的。
      • 强类型?您可能是指静态类型。他可以默默地从 const char ptr 转到 void 的事实使得 C++ 是静态的,但不是强类型的。有类型,但编译器不会说什么,除非你启用一些很可能不存在的模糊标志。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-24
      • 1970-01-01
      • 1970-01-01
      • 2010-12-16
      • 2013-02-15
      • 2011-05-24
      • 1970-01-01
      相关资源
      最近更新 更多