数据结构之hashtable

1、基本概念

  hashtable是一种存储成对的键值和实值元素的字典结构。不同于红黑树的对数级的平均时间消耗,hashtable提供常数级的时间消耗。

2、散列函数

  为了达到常数级的时间消耗,底层必须使用array这种连续空间的内存结构。想要存储元素,就需要将元素的键值映射成对应的索引值,实现这种功能的映射函数就称为散列函数(hash function)。散列函数的实现是很重要的,不仅影响着存储array的大小,还决定着存储array利用率等很多东西。

3、元素碰撞

  使用散列函数将元素的键值映射到一定区域内的索引位置,不可避免会出现不同的的键值得到相同的索引位置,这种情况就称为元素碰撞。当发生碰撞时,就需要采取一定的策略解决碰撞问题。
  3.1线性探测。
  当发生元素碰撞时,循环向下一个位置进行查找,到达尾端就跳转到头部继续查找,直到找到一个可以插入的空余位置。只要array够大,必然可以找到一个插入位置,但花费的时间就很难计算了。同理,在查找元素时也可能会花费大量的时间。同时,在碰撞发生后占用了碰撞位置附件其他元素的位置,导致这一区域的碰撞概率大大增加,从而形成恶性循环,我们称这种情况为主集团(primary cloustering)。
  3.2平方探测。
  为了解决主集团问题,我们在元素碰撞发生后,第1次移动一个位置,第2次移动4个位置,以此类推。由于是平方探测,为了保证必然可以找到一个插入位置,我们必须设定array的大小为质数。当保证负载系数在0.5以下时,没插入一个新元素的探测次数不超过两次。
  相对于线性探测,平方探测的计算消耗更加大,但我们可以对这一消耗进行优化。已知H(i)=H(0)+i*i(mod M),H(i-1)=H(0)+(i-1)*(i-1)(mod M),可得H(i)=H(i-1) + (2i -1)(mod M),通过上一个位置可快速计算出下一个查找位置。
  当负载系数超过0.5时,我们需要找出下一个新的且足够大的质数,对array进行扩充。此时并不是直接拷贝,而是扫描原有array内的元素,将其重新映射到新的array内。
  平方探测解决了主集团问题,但一样会造成某些区域的碰撞概率较高,从而形成次集团(secondary cloustering)。
  3.3双散列
  为了解决次集团,我们在元素碰撞发生后,使用另一个散列函数查找插入位置。第二个散列函数的选择至关重要,最好能保证所有的位置都可被探测到。当相对于平方探测,散列函数的计算是更加耗时,因而需要根据不同的情况进行具体的选择。
  3.4开链
  不同于前面的散列方法,开链的做法是将相同散列值的元素放到同一个list里,这样完全避免了碰撞的问题,但由于使用了链表进行元素的存储,需要花费一些额外的空间。使用开链,array的负载系数将大于1,对于空间的利用率更高。标准STL就是使用这一方法实现的hashtable。

4、实际应用

  hash_set和hash_map不属于标准库,但很多版本的STL都实现散列的版本,以下是测试用例:

hash_set<int> set;
set.insert(3);
set.insert(196);
set.insert(1);
set.insert(389);
set.insert(194);
set.insert(387);

hash_set<int>::iterator iter1 = set.begin();
hash_set<int>::iterator iter2 = set.end();
for (;iter1 != iter2;++iter1)
{
    cout<<*iter1<<" ";  //3 387 196 1 389 194
}
cout<<endl;

  这里需要注意的,由于hashtable的限制,有些数据类型是无法处理的,对应的hash_map也是无法处理的。例如设置类型为const char*时,不能使用缺省的equal_to(T),需要自己定义比较函数和哈希函数,以下为测试代码:

struct eqstr
{
    enum
    {    // parameters for hash table
        bucket_size = 4,    // 0 < bucket_size
        min_buckets = 8  // min_buckets = 2 ^^ N, 0 < N
    };    

    size_t operator()(const char* str) const
    {
            unsigned long h = 0;
            for (;*str;++str)
            {
                h = 5*h + *str;
            }
            return size_t(h);
    }

    bool operator()(const char* str1,const char* str2) const
    {
        return strcmp(str1,str2) == 0;
    }
};

typedef hash_map<const char*,int,eqstr> StrHashMap;
StrHashMap map2;
map2["a"] = 1;
map2["b"] = 3;
map2["c"] = 7;
map2["d"] = 4;
StrHashMap::iterator iter5 = map2.begin();
StrHashMap::iterator iter6 = map2.end();
for (;iter5 != iter6;++iter5)
{
    cout<<(*iter5).first<<" "<<(*iter5).second<<endl;
    /*a 1
      b 3
      c 7
      d 4*/
}
cout<<endl;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值