在C ++中使用HashMap的最佳方法是什么?


174

我知道STL有一个HashMap API,但是我找不到有关此方面的良好示例的任何详尽的文档。

任何好的例子将不胜感激。


您是在询问C ++ 1x hash_map还是std :: map?
philant 2010年

2
我想要类似C ++中的java.util.HashMap之类的东西,如果有的话,可以采用标准化的方式来实现。最好的非标准库。C ++开发人员在需要HashMap时通常使用什么?
user855 2010年

Answers:


237

标准库包括有序和无序的map(std::mapand 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 类似的功能(例如,在上面的示例中,您只需搜索并替换mapunordered_map)。

unordered_map容器是C ++ 11标准修订版引入的。因此,根据您的编译器,您必须启用C ++ 11功能(例如,使用GCC 4.8时,必须添加-std=c++11到CXXFLAGS)。

甚至在C ++ 11发行版之前unordered_map,在命名空间中都支持GCC std::tr1。因此,对于旧的GCC编译器,您可以尝试像这样使用它:

#include <tr1/unordered_map>

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

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


1
虽然标准库没有基于表散列的容器中,几乎所有的实现包括hash_map在某种形式的SGI STL。
James McNellis 2010年

@JamesMcNellis,建议将其用于HashMap实现unordered_map或hash_map
Shameel Mohamed

2
@ ShameelMohamed,2017年,即,在C ++ 11之后的6年,应该很难找到一个不提供STL的STL unordered_map。因此,没有理由考虑非标准hash_map
maxschlepzig

30

A hash_map是用于标准化目的的较旧的未标准化版本,称为a unordered_map(最初在TR1中,自C ++ 11起已包含在标准中)。顾名思义,它与std::map无序的主要区别在于-例如,如果您从begin()到遍历映射end(),则可以通过键1来按顺序获取项目,但是如果unordered_mapbegin()到遍历则可以从end()a中获取项目。或多或少的任意顺序。

unordered_map通常期望An 具有恒定的复杂性。也就是说,无论表中有多少项,插入,查找等操作通常都需要花费固定的时间。一个std::map具有复杂性的对数上存储项目的数量-这意味着插入或检索项目长的时间,但非常缓慢,随着地图变得更大。例如,如果查找一百万个项目中的一个要花费1微秒,那么您可以期望花费约2微秒来查找200万个项目中的一个,400万个项目中的一个要花费3微秒,800万个项目中的一个要花费4微秒。物品等

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


1std::less<T>默认情况下,在创建地图时由第三个模板参数定义顺序的位置。


1
我知道我要在答案发布后9年了,但是...您是否有一个文档链接,其中提到无序地图可能会缩小尺寸这一事实?通常,std集合只会增长。此外,如果您插入大量数据但事先或多或少地知道要插入多少键,则可以在创建时指定地图的大小,这基本上使调整大小成本无效(因为不会有任何调整) 。
Zonko

@Zonko:对不起,当我被问到时我没有注意到这一点。据我所知,除非响应call,否则unordered_map不会收缩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。那正是他们的目的。
Guyarad

强类型?您可能是静态输入。他可以从const char ptr静默变为void的事实使C ++成为静态类型,但不是强类型。有类型,但是除非您启用一些很可能不存在的晦涩标记,否则编译器不会说什么。
Sahsahae

6

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

https://stackoverflow.com/a/3578247/895245中提到了此问题,但在以下答案中:在C ++中std :: map内包含什么数据结构?我通过以下方式为GCC stdlibc ++ 6.4实现提供了进一步的证据:

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

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

在此处输入图片说明

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

这个答案很明确:使用自定义类类型作为键的C ++ unordered_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. 在标准名称空间下,声明一个名为hash的模板结构,其类名为类型(请参见下文)。我发现了一个很棒的博客文章,其中还展示了使用XOR和位移位计算哈希的示例,但这超出了此问题的范围,但是其中还包括有关如何完成使用哈希函数的详细说明,以及https://prateekvjoshi.com/ 2014/06/05 /在C中为用户定义的类使用哈希函数/

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