• STL哈希表


    哈希表

    相关概念

    避免碰撞 — 构造哈希函数、再散列函数法、哈希表加链表

    img

    1. 虽然很好的处理了碰撞的问题,但是当单链表很长时,遍历链表的速度会很慢。
    2. 当链表太长时(经验法则:当元素个数比 bucket 数还要多时),想办法将其打散 — 将 bucket 扩充大约两倍。选择53的倍数附近的素数 53 -> 97 -> 193........。(G2.9适用,其他的不一定。)
    3. 哈希表的成长过程:bucket 会增加、 所有的元素所在的 bucket 会重新计算一遍,会花费一定的时间

    img

    1. HashFcn 由元素对象抽象出哈希码的函数
    2. ExtraKey 如何从元素(可能是 pair 等)取出 key 值的函数
    3. EqualKey 如何判断 key 相等的函数

    哈希表本身的大小为20个字节:3个函数对象、1个 Vector、1个 unsigned int。

    hashtable_node 为节点构成的单向链表(VC中为双向链表)。

    迭代器必须实现智能的 ++ 等操作,在链表末端后重新定位回 bucket ,其中有两个指针,一个指向节点,一个指向哈希表本身。

    //Application
    hashtable<const char*,
              const char*,
              hash<const char*>,
              identity<const char*>,
              eqstr,
              alloc>
    ht(50,hash<const char*>(),eqstr());
    
    ht.insert_unique("kiwi");
    ht.insert_unique("plum");
    ht.insert_unique("apple");
    
    struct eqstr{
        bool operator()(const char* s1,
                        const char* s2,) const
        {return strcmp(s1,s2) == 0;}
    };
    

    关于 hashfcn:当模板参数为简单类型时,传入的简单类型的值可以直接作为抽象的哈希编码,其他:

    img

    其中的 hash_string(处理 c 风格字符串) 方案也是经验设计。

    hash code%number_of_bucket 的除数取余算法方案是业内公认的做法

    img

    find 和 operator 是哈希表中的一些成员函数,其中要计算元素会落在哪个篮子里。按其调用的路径,最后都会归结到 hash(key)%n

    首先生成哈希值,然后使用hash code%number_of_bucket计算元素位置

    hash_set与hash_multiset

    相关概念

    1. hash_set是以hashtable为底层机制实现的。故对hash_set的各种操作可以转调用hashtable来实现。
    2. hash_set与set的不同:hash_set的底层机制是hashtable,而set的底层机制是RB-tree;set的元素能够自动排序,而hash_set的元素没有排序功能
    3. hash_set中键值就是实值,实值就是键值。
    4. hash_multiset的与hash_set有很大的相同性,其唯一差别就是hash_multiset中的元素可以重复。hash_set中的插入函数使用的是hashtable中的insert_unique(),而hash_multiset中的插入函数使用的是hashtable中的insert_equal()。

    实现

    // hash_set类定义
    template <class Value, class HashFcn = hash<Value>,
              class EqualKey = equal_to<Value>,
              class Alloc = alloc>
    class hash_set
    {
    private:
      typedef hashtable<Value, Value, HashFcn, identity<Value>, 
                        EqualKey, Alloc> ht;
      ht rep;        //底层机制 hashtable
    
    public:
    ....
      hash_set() : rep(100, hasher(), key_equal()) {}    //缺省使用100个篮子,
                                                         //最后将被调整成对应的质数193
    
      hash_set(size_type n, const hasher& hf) : rep(n, hf, key_equal()) {}    //构造函数
      hash_set(size_type n, const hasher& hf, const key_equal& eql)           //构造函数
        : rep(n, hf, eql) {}
    
    //方法,通过hash table实现
      size_type size() const { return rep.size(); }
      size_type max_size() const { return rep.max_size(); }
      bool empty() const { return rep.empty(); }
      void swap(hash_set& hs) { rep.swap(hs.rep); }
      friend bool operator== __STL_NULL_TMPL_ARGS (const hash_set&,
                                                   const hash_set&);
    
      iterator begin() const { return rep.begin(); }
      iterator end() const { return rep.end(); }
    

    hash_map 与 hash_multimap

    相关概念

    SGI在STL标准外提供了hash_map,以hashtable作为底层机制。运用map为的是能够根据键值快速搜索元素,这一点RB-tree或是hashtable都能够实现。但RB-tree有自动排序能力而hashtable没有,其结果就是set元素会自动排序而hash_set不会。map和hash_map一样,拥有键和值,它们的使用方法也一样。
    需要很简单:
    1、hash函数(库有则使用库,没有则自己提供仿函数)。
    2、key大小比较函数(库有,则使用库提供的模板实例化类,否则自己编写仿函数)
    3、使用很简单,插入的时候,注意 insert 和 [ ] 方法的不同。

    实现

    int main(void){
        hash_map<const char * , int , __gnu_cxx::hash<const char*> , eqstr> Mymap;
        Mymap["January"] = 31;//设置map特有的操作符重载,下面重点讲解,将hashmap键和值可以用来计算键的次数
        Mymap["February"] = 28;
        Mymap["March"] = 31;
        Mymap["April"] = 30;
        Mymap["May"] = 31;
        Mymap["June"] = 30;
        Mymap["July"] = 31;
        Mymap["August"] = 31;
        Mymap["September"] = 30;
        Mymap["October"] = 31;
        Mymap["November"] = 30;
        Mymap.insert( pair<const char * , int>("December" , 31) );//产生临时对象,并pair起来
    
        //测试输出对应的键和值
        cout << "December " << Mymap["December"] << endl;
        cout << "June " << Mymap["June"] << endl;
        cout << "April " << Mymap["April"] << endl;
        cout << "March " << Mymap["March"] << endl;
         
        cout << endl << "测试遍历操作" << endl;
        for(hash_map<const char * , int , __gnu_cxx::hash<const char*> , eqstr>::const_iterator ite = Mymap.begin() ; ite!= Mymap.end() ; ite++)
            cout << (*ite).first << "  " << ite->second << endl;//记住这两种操作符重载类型
        cout << endl;
    
    }
    

    这是最重要的地方,通过key获取对应的value。如果key存在,则返回value的引用,如果key不存在,则插入并且将value初始化为默认值并返回其引用,对应int,就相当于int()那么就是0。

    T& operator[](const key_type& key) {
        return rep.find_or_insert(value_type(key, T())).second;
      }//这个是最重要的方法,区别与set的,很重要,直接调用了find_or_insert
    

    find_or_insert的功能在前一讲hashtable已经详细说明,反正返回的是hashtable第一个模板参数类型。若key存在则返回存在的,否则插入hashtable并返回对应的引用。
    所以这里可以将IP地址作为key,IP出现的频率作为value,用来统计每个IP出现的频率,在大数据的时候特别有用,所以这个就是map牛逼的地方,存储和查找都非常块。

    img

  • 相关阅读:
    Java中的subList方法
    某同学工作之后的感悟
    存放80000000学生成绩的集合,怎么统计平均分性能高
    为了金秋那沉甸甸的麦穗,我绝不辜负春天
    subList?? subString???
    "爸妈没多大本事"……
    中秋节支付宝口令红包解析
    算法>动态规划(一) 小强斋
    数据结构>优先队列(堆) 小强斋
    算法>贪心算法 小强斋
  • 原文地址:https://www.cnblogs.com/LuckCoder/p/14532980.html
Copyright © 2020-2023  润新知