zoukankan      html  css  js  c++  java
  • 一个万用的hashfunction

    C++11里新的容器unorderedmap,其底层是一个hashtable,在C++98中,unorderedmap其实已经有过,叫做hashmap。

    unorderedmap(hashmap)是一个模板,模板参数有5个,以下是可能的伪代码(不同的编译器有不同的实现)

    1 template <class Key, 
    2           class Value,
    3           class HashFcn = hash<Key>,
    4           class EqualKey = equals_to<Key>,
    5           class Alloc = alloc>
    6 class hash_map
    7 {
    8     ...
    9 };

    Key是键值类型,Value是实值类型,HashFcn是一个用来寻找bucket位置的函数,稍后重点讲述,EqualKey是用来确定两个对象如何算相等,Alloc是内存分配相关。

    hashtale实际上是由一层vector形成一个bucket的集合,每个bucket上挂有一个链表,在不同编译器下链表实现可能不同。既是所谓的sperate chaning开链法,分开链接。

    一个元素想要插入到hashtable,必须要算出挂在哪一个bucket上,再插入到bucket上的链表后端。

    计算出插入到哪一个bucket上的操作,便是hashfunction的工作。

    hashmap是通过Key来决定一个元素的在hashtable中的位置的,因此Key的类型尤为关键。

    如果Key是一个简单的系统类型,比如int,char。那么STL已经帮我们定义好了对应的hashfunction。

    class HashFcn = hash<Key>

    我们注意到HashFcn模板的缺省参数hash<Key>,便是STL给我们实现好的hashfunciton。

    举例hashfunction之前,先用讲到一个计算元素在bucket落脚处的函数bkt_num_key

    1 size_type bkt_num_key(const key_type& key, size_t n) const
    2 {
    3     return hash(key) % n;
    4 }

    STL最终都会走到hash(key)来计算元素的bucket位置。

    STL通过偏特化的方式实现了一些简单类型的hashfunction。举几个例子:

    1 template <class Key> struct hash {};
    2 
    3 template<> struct hash<char> { 
    4     size_t operator()(char x) const { return x; }
    5 };
    6 
    7 template<> struct hash<int> { 
    8     size_t operator()(int x) const { return x; }
    9 };

    那么其实简单类型的hashfunction就是返回自己本身。有稍微复杂一些的char*

     1 template<> struct hash<const char*> {
     2     size_t operator()(const char* s) const { return __stl_hash_string; }
     3 }
     4 
     5 // hash function越乱越好
     6 inline size_t __stl_hash_string(const char* s)
     7 {
     8     unsigned_long h = 0;
     9     for (: *s; ++s)
    10         h = 5*h + *s;
    11         
    12     return size_t(h);
    13 }

    那么如果一个Key是自定义的类型,hashfunction应该怎么做呢。

    假定我们的自定义类型如下:

    struct CustomerInfo
    {
        int x = 0;
        int y = 0;
        int z = 0;
    }

    这个自定义结构当成unordermap的key,如果你不定义hashfunction应该是不能过编译的。

    也许你会简单想一个hashfunction,三个int分别用STL的hashfunction版本,然后加起来!天才=_=。

     1 struct HashFuncCustomer
     2 {
     3     std::size_t operator()(const CustomerInfo &c) const
     4     {
     5         using std::size_t;
     6         using std::hash;
     7 
     8         return hash<int>()(c.x)
     9             + hash<int>()(c.y)
    10             + hash<int>()(c.z));
    11     }
    12 };

    能过编译。但是其实这个hashfunction所造成的碰撞是很多的。hashfunction的目标应该是越散越好,因此哈希表又叫散列表。

    碰撞的时候,新加的元素会挂在bucket碰撞位置的链表后面。hashtable的查找复杂度应该是O(1)~O(n),明显碰撞越多复杂度越趋近于O(n)。

    TR1版本(简单理解成C++98和C++11之间的过渡版本)提供了一种万用的hashfunction,外层调用如下即可:

     1 struct HashFuncCustomer
     2 {
     3     std::size_t operator()(const CustomerInfo &c) const
     4     {
     5         using std::size_t;
     6         using std::hash;
     7 
     8         return hash_value(c.x, c.y, c.z);
     9     }
    10 };

    内部的实现其实并不重要,内部的实现是个数学问题=_=。这里面内部用了黄金分割比例,移位等各种操作。

    另外一个版本,我在实际项目中使用过的一个版本:

     1 struct HashFuncCustomer
     2 {
     3     std::size_t operator()(const CustomerInfo &c) const
     4     {
     5         using std::size_t;
     6         using std::hash;
     7 
     8         return ((hash<int>()(c.x)
     9             ^ (hash<int>()(c.y) << 1)) >> 1)
    10             ^ (hash<int>()(c.z) << 1);
    11     }
    12 };

    仅供参考。

    hashfunction怎么写的好,应该是个数学问题。

  • 相关阅读:
    ubuntu ip
    [转]caffe+Ubuntu14.0.4 64bit 环境配置说明(无CUDA,caffe在CPU下运行) --for --Amd
    kali install fcitx
    [转译][马基 杰斯特(MarkeyJester) 摩托罗拉68000 入门教程] 叁
    [转译][马基 杰斯特(MarkeyJester) 摩托罗拉68000 入门教程] 叁
    [转译][马基 杰斯特(MarkeyJester) 摩托罗拉68000 入门教程] 叁
    [转译][马基 杰斯特(MarkeyJester) 摩托罗拉68000 入门教程] 叁
    [转译][马基 杰斯特(MarkeyJester) 摩托罗拉68000 入门教程] 贰
    [转译][马基 杰斯特(MarkeyJester) 摩托罗拉68000 入门教程] 贰
    [转译][马基 杰斯特(MarkeyJester) 摩托罗拉68000 入门教程] 贰
  • 原文地址:https://www.cnblogs.com/yao2yaoblog/p/12470402.html
Copyright © 2011-2022 走看看