C++ STL标准库与泛型编程(六)哈希表、Unordered 容器

哈希表

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

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

  • HashFcn 由元素对象抽象出哈希码的函数
  • ExtraKey 如何从元素(可能是 pair 等)取出 key 值的函数
  • 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;}
};

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

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

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

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

hash_set与hash_multiset

一、概念

  1. hash_set是以hashtable为底层机制实现的。故对hash_set的各种操作可以转调用hashtable来实现
  2. hash_set与set的不同:1)hash_set的底层机制是hashtable,而set的底层机制是RB-tree;2)set的元素能够自动排序,而hash_set的元素没有排序功能。hash_set中键值就是实值,实值就是键值。
  3. 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牛逼的地方,存储和查找都非常块。

不定序容器初始化时,必须指定 hash_function()。
--------------------- 

参考 : 
作者:有时需要偏执狂 
来源:CSDN 
原文:https://blog.csdn.net/u010710458/article/details/79682266 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/SimonxxSun/article/details/85306288
今日推荐