SGISTL源码阅读二十二 hashtable上

SGISTL源码阅读二十二 hashtable上

前言

hashtable的部分基本知识

hashtable,哈希表,也可称为散列表。
哈希表用于以常数平均时间执行插入、删除和查找操作,每个关键字按照某种规定被映射到从0-TableSize-1这个范围中的某个数,并且被放到适当的单元中。
时间复杂度可以达到常数级别听起来让人觉得很兴奋,但是它也是需要付出代价的,哈希表的一个最重要的问题就是解决冲突,如果一个关键字刚好能被映射到一个空位置上,那当然是极好的了,但是如果这个位置上已经存入了其他的关键字呢?
所以实现hashtable最重要的两个东西就是哈希函数(指定某种创建hashtable的规则)和解决冲突。

常用的hash函数
  1. 直接定址法:取关键字或者关键字的某个线性函数为散列地址,比如hash(k) = k或hash(k) = a * k + b,a、b为常数。
  2. 数字分析法:假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
  3. 平方取中法:取关键字平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中的哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。
  4. 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
解决冲突
  • 分离连接法
    分离链接的做法是将映射到同一个位置的关键字放在一个链表当中。
    在这里插入图片描述

  • 开放定址法
    开放地址散列法的基本思想是,遇到了冲突,我们就用另一套法则,将此关键字放在其他的空缺位置上。显而易见,他的缺点是散列表的创建必须足够大,才能够容许我们进行相关的操作,比起分离链接法来讲,虽然算法速度上有所提升,但是__内存浪费__比较大。因为他的装填因子λ应该低于0.5。
    装填因子λ:散列表中的元素个数与散列表大小的比值。
    开放定址法的实现也有多种
    1.线性探测法
    在原来的散列表基础之上添加增量序列,(1,2……,TableSize-1)循环试探下一个存储地址。
    2.平方探测 – 二次探测
    平方探测法:以增量序列1²,-1²,2²,-2²,……,q²,-q² 且 q≤|TableSize/2|循环试探下一个存储地址


以上仅为部分hashtable的知识,这篇文章以分析STL中实现的hashtable为主
在SGISTL中,hashtbale解决充吐的方法是分离链接法(叫法可能不同),称hash table表格内的元素为桶子(bucket),因为表格内的单元不仅仅是一个节点,可能有多个节点存在同一个bucket里面。
下面我们通过源码阅读来学习它。

深入源码STL中的hashtable

哈希函数
inline size_t __stl_hash_string(const char* s)
{
  unsigned long h = 0;
  for ( ; *s; ++s)
    h = 5*h + *s;

  return size_t(h);
}

__STL_TEMPLATE_NULL struct hash<char*>
{
  size_t operator()(const char* s) const { return __stl_hash_string(s); }
};

__STL_TEMPLATE_NULL struct hash<const char*>
{
  size_t operator()(const char* s) const { return __stl_hash_string(s); }
};

__STL_TEMPLATE_NULL struct hash<char> {
  size_t operator()(char x) const { return x; }
};
__STL_TEMPLATE_NULL struct hash<unsigned char> {
  size_t operator()(unsigned char x) const { return x; }
};
__STL_TEMPLATE_NULL struct hash<signed char> {
  size_t operator()(unsigned char x) const { return x; }
};
__STL_TEMPLATE_NULL struct hash<short> {
  size_t operator()(short x) const { return x; }
};
__STL_TEMPLATE_NULL struct hash<unsigned short> {
  size_t operator()(unsigned short x) const { return x; }
};
__STL_TEMPLATE_NULL struct hash<int> {
  size_t operator()(int x) const { return x; }

我们可以看到对于普通的数字或者char类型,SGISTL使用的哈希函数是直接定址法,并且根据不同的关键字有着不同的重载版本。
string的操作是比较特殊的。

hashtable的数据结构
//hashtable的一个结点
template <class Value>
struct __hashtable_node
{
  __hashtable_node* next;
  Value val;
};
//...
//缺省使用了SGISTL的空间配置器
template <class Value, class Key, class HashFcn,
          class ExtractKey, class EqualKey,
          class Alloc>
class hashtable {
public:
  //声明一些别名
  typedef Key key_type;		//节点的键值
  typedef Value value_type;	//节点的实值
  typedef HashFcn hasher;	//hash function的函数型别
  typedef EqualKey key_equal;	//从节点中取出键值的方法

  typedef size_t            size_type;
  typedef ptrdiff_t         difference_type;
  typedef value_type*       pointer;
  typedef const value_type* const_pointer;
  typedef value_type&       reference;
  typedef const value_type& const_reference;

  hasher hash_funct() const { return hash; }
  key_equal key_eq() const { return equals; }

private:
  hasher hash;
  key_equal equals;
  ExtractKey get_key;
  //声明别名
  typedef __hashtable_node<Value> node;
  typedef simple_alloc<node, Alloc> node_allocator;
  //buckets,之前提到过的桶子,这里是一堆桶子,是用vector存储的
  //桶子里面放的是节点
  vector<node*,Alloc> buckets;
桶子的数量

SGISTL以28个质数来设计桶的数量。(从53开始,左键呈现大约两倍的关系)。

//把这28个质数放在一个数组当中可供随时访问
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
  53,         97,           193,         389,       769,
  1543,       3079,         6151,        12289,     24593,
  49157,      98317,        196613,      393241,    786433,
  1572869,    3145739,      6291469,     12582917,  25165843,
  50331653,   100663319,    201326611,   402653189, 805306457,
  1610612741, 3221225473ul, 4294967291ul
};
//用来返回这28个质数中最接近且大于n的一个质数
inline unsigned long __stl_next_prime(unsigned long n)
{
  const unsigned long* first = __stl_prime_list;
  const unsigned long* last = __stl_prime_list + __stl_num_primes;
  const unsigned long* pos = lower_bound(first, last, n);
  return pos == last ? *(last - 1) : *pos;
}
hashtable的构造函数
public:
  hashtable(size_type n,
            const HashFcn&    hf,
            const EqualKey&   eql,
            const ExtractKey& ext)
    : hash(hf), equals(eql), get_key(ext), num_elements(0)
  {
    initialize_buckets(n);
  }

  hashtable(size_type n,
            const HashFcn&    hf,
            const EqualKey&   eql)
    : hash(hf), equals(eql), get_key(ExtractKey()), num_elements(0)
  {
    initialize_buckets(n);
  }
  //拷贝构造函数
  hashtable(const hashtable& ht)
    : hash(ht.hash), equals(ht.equals), get_key(ht.get_key), num_elements(0)
  {
    copy_from(ht);
  }
  //...
  //以桶数量n来初始化桶
  void initialize_buckets(size_type n)
  {
    //从28个质数中找到一个合适的值作为桶的数量
    const size_type n_buckets = next_size(n);
    //调用vector的函数
    //reserve是容器预留空间,但在空间内不真正创建元素对象,所以在没有添加新的对象之前,不能引用容器内的元素。
    buckets.reserve(n_buckets);
    //将所有值置0(类型为node*)
    buckets.insert(buckets.end(), n_buckets, (node*) 0);
    num_elements = 0;
  }
  //...
  template <class V, class K, class HF, class Ex, class Eq, class A>
void hashtable<V, K, HF, Ex, Eq, A>::copy_from(const hashtable& ht)
{
  //先将当前桶数量清空
  buckets.clear();
  //和initialize_buckets的操作相同
  buckets.reserve(ht.buckets.size());
  buckets.insert(buckets.end(), ht.buckets.size(), (node*) 0);
  __STL_TRY {
    //依次复制hashtable ht中桶的节点到当前hashtable
    for (size_type i = 0; i < ht.buckets.size(); ++i) {
      if (const node* cur = ht.buckets[i]) {
        node* copy = new_node(cur->val);
        buckets[i] = copy;

        for (node* next = cur->next; next; cur = next, next = cur->next) {
          copy->next = new_node(next->val);
          copy = copy->next;
        }
      }
    }
    num_elements = ht.num_elements;
  }
  //处理异常情况
  __STL_UNWIND(clear());

总结

通过以上学习,我们对hashtable已经有了一个比较清除的认识,其实hashtable也可以认为是一种字典结构,一个关键字对应着一个映射。
接下来我们将继续学习hashtable的迭代器以及它的相关操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值