C++STL中的hash_map 哈希表

map与hash_map

map与hash_map都是在C++STL中常用的数据结构。
map:存储数据结构是采用红黑树实现,提供了key-value的存储和查找功能,查找速度可达log(n)。
hash_map:基于hash_table(哈希表)储存,相对map来说,他的查找速度大大的降低,几乎可以看成是常熟时间;但是代价就是消耗更多的内存(但是在现在内存越来越大的情况下,用内存换时间的选择十分值得)。

他们之间的区别:

  • 构造函数:hash_map需要hash函数,比较函数(等于函数);map只需要比较函数(小于函数)。
  • 存储结构:hash_map采用hash表存储,map一般采用红黑树(RB Tree)实现。

什么时候选择hash_map和map(使用场景):

  • hash_map 查找速度会比map快,而且查找速度基本为数据数据量大小,属于常数级别。
  • map的查找速度是log(n)级别。

但这并不意味着一定常数就比log(n)小:因为在hash_map中,每一个元素的存储都需要经过hash函数转换为对应的hash值,这些都是需要时间的。
如果考虑查找效率并对内存大小没有要求,当元素达到一定数量级时,考虑hash_map。
如果要求尽可能少消耗内存,那么hash_map可能会不那么适合,特别是当hash_map对象特别多时,内存消耗就更大了,而且hash_map的构造速度也比较慢。

总的来说,权衡他们因素有三个:查找速度, 数据量, 内存使用

hash_map

hash_map需要一个hash函数和比较函数(如果不提供,在符合的情况下,STL会为我们提供缺省的函数):使用一个下标范围比较大的数组来存储元素其中的每个元素的关键字都与一个hash值(即数组下标)相对应,于是用这个数组单元来存储这个元素,这个数组单元称之为“桶。
那么如何获得hash值呢:在存储数据时,hash函数会根据数据的key值得到该数据的hash值。

但也有例外的情况:hash值和元素关键字并不一定是一一对应的,根据hash函数很可能会对不同的元素的key生成相同的hash值,从而把不同的元素放在了相同的“桶”,这种情况称之为“冲突”。因此哈希表有“直接定址”与“解决冲突”两个特点。

hash_map会在一开始分配一大片内存,形成许多“桶”。在之后插入元素时,根据以下步骤:

  1. 获取元素的key值
  2. 根据key值通过hash函数得到hash值
  3. 得到该hash值的“桶”号
  4. 将元素(key和value)存放在“桶”中

而查找元素的步骤为:

  1. 获得key值
  2. 根据key值通过hash函数得到hash值
  3. 得到相应的“桶”号
  4. 比较桶内元素是否与key值相同
  5. (若存在该key对应的数据)取出该key的value

使用hash_map

#include<hash_map>
//细节省略......
hash_map<string, int> hashmap;
//STL会为我们缺省的提供hash函数与比较函数
//该声明等同于hash_map<string, int, hash<string>, equal_to<int> > mymap;

C++支持的提供缺省hash函数对应的数据类型有:char*,const char*,char,unsigned char,signed char,short,unsigned short,int ,unsigned int,long ,unsigned long
也就是说,以上的数据类型,STL会为我们提供缺省的hash函数;而对于其他的数据类型,我们需要自己提供(例如string):

自定义hash函数

struct MyHash{
        size_t operator()(const string& str) const
        {
                unsigned long hash_value = 0;
                for (size_t i = 0 ; i < str.size() ; i ++)
                hash_value= 5*hash_value+ str[i];
                return size_t(hash_value);
        }
};

声明自己的hash函数需要遵循以下几点:

  1. 使用strut ,并重载operator()
  2. 返回 size_t
  3. 形参需要为hash的key的数据类型
  4. const函数

写出了自己的hash函数,上面的hash_map声明需要改为:

hash_map<string, int , MyHash> hashmap;

接下来写比较函数:

自定义比较函数

在hash_map中,要比较桶内的数据和key是否相等 。
假如我们存储的是一个自定义数据类型:
第一种设计比较函数的方法,就是重载==操作符,使用equal_to比较:

struct stu{
	int ID;
	int data;
	//重载==
	bool operator == (const stu & Temp){
		return ((ID==Temp.ID)&&(data==Temp.data));
	}
};

重载了stu数据结构的==之后,使用equal_to

hash_map<string, stu, MyHash,equal_to<stu>> hashmap;

equal_to的定义:
template <class _Tp>
struct equal_to : public binary_function<_Tp,_Tp,bool>
{
bool operator()(const _Tp& __x, const _Tp& __y) const { return __x == __y; }
};

另一种方法,就是写一个函数对象

struct compare_func{
        bool operator()(const char* s1,const char* s2) const{
                return strcmp(s1,s2)==0;        
        }
};

在有了conpare_func之后,就可以使用hash_map了:

hash_map<const char*a,string , hash<const char*>, compare_func> StrMap;

使用typedef定义hash_map

typedef hash_map<const char*, string, hash<const char*>, compare_str> StrMap;
//..........
StrMap mymap;
mymap["ABC"]="aaaaaaa";
mymap["EFG"]="bbbbbbb";
//..........

因此如果我们想在hash_map中加入自己的数据类型,我们只需要做两件事:你只要做两件事,定义hash函数,定义比较(等于)函数

hash_map 函数

hash_map的函数和map的函数差别不大,以下为常用的函数

  1. hash_map(size_type n):为了程序运行效率,这个参数十分重要,n为hash桶的个数,个数越多,hash_map发生冲突的概率就越小,效率也越高;但相应的内存消耗会变大。
  2. const_iterator find(const key_type& k) const:用于查找,参数为key值,返回一个该参数位置的迭代器。
  3. data_type&operator[](const key_type& k ):类似map的[key]下标查找,但注意的是与map一样,若无对应key的数据,会增加一个key的元素。
  4. const_iterator find(const key& key) const:find()函数可与上一条[key]下标查找相对应,若无该key元素存在,他不会增加一个该key的元素。
  5. insert 函数:在容器中不包含key值时,insert函数和[]操作符的功能差不多,但注意的是,当元素越来越多,为了效率,程序会申请更多的内存以存放更多的桶及其元素,这时在insert后,iterator不一定依然有效。
  6. erase 函数:但在SGI STL中,erase并不会自动回收内存,因此调用erase后,其他元素的iterator还是可用的。

相关的hash容器

容器有set, multimap, multiset
hash 容器除了hash_map之外,还有hash_set, hash_multimap, has_multiset
这些容器使用起来和(set, multimap, multiset的区别)与(hash_map和map的区别)一样。

猜你喜欢

转载自blog.csdn.net/qq_43265890/article/details/83278771