数据结构之hashtable

来源:互联网 发布:加热脚垫淘宝 编辑:程序博客网 时间:2024/05/20 20:19

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;
原创粉丝点击