C++STL源码剖析(侯捷)(六) hashtable、无序容器

本文详细探讨了哈希表的工作原理,包括哈希函数、再散列和链表解决碰撞问题的方法。介绍了哈希表成长过程中的bucket扩充策略,以及哈希函数、哈希码的计算方式。此外,还对比了hash_set与set、hash_map与map的区别,以及如何在实际应用中使用这些数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

哈希表

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

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

4.预先存好要用的素数,需要时直接扩充。{53,97,。。。}

  • HashFcn是用来计算元素放的位置,即HashCode。
  • ExtractKey用来取出(pair中的)key值的函数。
  • EqualKey比较key值相等的函数。

1.hash,equals,get_key是根据传递进来的类型创建出来的三个函数对象。

2.cur指向表中的某一元素,而不是指向buckets。

3.走到一个链表尽头后,要回到哈希表本身,因此需要ht指向buckets,进入下一个篮子。

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

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

这里为什么不直接传入strcmp函数,而要包装成一个eqstr呢?因为此处哈希表中EqualKey要求的是ture or false,而strcmp函数传回的参数是-1,0,1,所以必须加一层外套。

 哈希函数,哈希码

泛化 :hash{}

特化:传入的是具体类型,hash函数不一样。

modulus运算(余数运算)

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:
  typedef typename ht::key_type key_type;//迭代器声明,用hashtable的
  typedef typename ht::value_type value_type;
  typedef typename ht::hasher hasher;
  typedef typename ht::key_equal key_equal;

  typedef typename ht::size_type size_type;
  typedef typename ht::difference_type difference_type;
  ...
  hasher hash_funct() const {return rep.hash_funct();}
  key_equal key_eq() const {return rep.key_eq();}

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 和 [ ] 方法的不同。

二.测试hash_map

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值