Hash Table
Hash表是一个特点很突出的底层容器,虽然STL中没有专门的叫做Hash表的容器,但是像unordered_map和unordered_set这些标准容器都是以Hash表为底层容器实现的.Hash表具有平均O(1)的元素访问能力,包括存取数据和查找数据,相比于vector容器它的有点就是O(1)的时间复杂度查找数据.Hash表的缺点也就是存放的数据与存入的顺序没有关系而且无法获得存放的数据之间的大小关系.
Hash表的实现主要就是通过将键值key通过hash函数映射成存放位置的坐标然后存放数据,这里可能会出现不同的key映射到同一个坐标的情况,对于这种情况的处理方式一般有线性检测,二次检测以及开链法等手段,我们这里SGI中的冲突避免策略就是采用的开链法,即hash出来的相同key按照链表的组织方式挂载到对应位置的槽上.
Hash Table设计思路
Hash Table类的原型
这里需要解释一下,Key指的就是键的类型,Value指的是存储的数据类型,对于unordered_set<int>
这种,对应过来的Value就是int,而对于unordered_map<int, double>
这种对应过来的Value就是pair<int,double>
. HashFunc即hash仿函数类.EqualKey也是一个仿函数类,它的作用就是从Value类型中提取出Key类型对应的值,比如Value如果是int,那么提取出int,如果Value是pair<int,double>
那么就是提取出int,这在后面的unordered_map类可以再仔细看一下.EqualKey也是一个仿函数类,用来判断两个key是否相等.Alloc类即是负责空间分配的类
数据组织形式
由于我们采用开链法,所以我们需要先预申请k个(一般是个素数)顺序存储的槽(bucket),然后每个槽存放一个链表指针用来索引hash到该槽的所有数据.然后每次添加数据时候都是先hash到对应的槽然后加入到该槽链表的尾端;如果是查找某个key也是同样hash到对应的槽然后遍历该槽的链表查看是否存在该key的数据.
这里贴一下链表和槽的数据结构代码实现:
另外,为了避免冲突发生率太高,当hash表的负载因子(表中存放的数据总数/槽的个数)超过一定阀值(通常是1)就需要扩大槽的数目(一般是原槽数的2倍左右的一个素数).具体扩大的方法就是,申请新的槽,然后把原来槽中存放的数据全部重新hash到新槽对应的链表中,然后释放原来的槽及数据.
迭代器设计
根据上面的数据组织形式,我们可以让迭代器关联到槽中链表上的链表节点,然后根据需要后移所在链表中的位置或者转移到下一个槽中的链表头部.所以迭代器类需要两个信息,一个是Hash Table对象指针以便更换槽;另一个是当前所在的链表节点.
这里贴上迭代器的设计代码:
Hash Table具体设计
具体代码如下面所示,其实完整的工程代码请查看我的github上的Tiny-STL项目.
根据Hash Table实现unordered_set和unordered_map
有了上面的Hash Table底层类再来实现unordered_set和unordered_map简直不要太轻松.我们只需要准备好Hash Table所需要的Key, Value, ExtractKey等类参数然后包装一层API即可.
unordered_set
其中由于unordered_set的值就是Key,所以Value的类型就是Key,ExtractKey也很就很简单(extract_key_single),代码如下:
unordered_map
我们在使用unordered_map时候一般会确定两个类型,比如unordered_map<int,double>,这里int是Key类型而double是Mapped类型,所以传递给Hash Table的Value类型就是pair<Key, Mapped>即pair<int, double>,同样对于ExtractKey(使用extract_key_pair)也需要注意一下.
总结
只要你把Key, Value各自的作用搞得很清楚,然后参照上面提到的数据存储方法就可以很清楚的写出相关代码,另外从这个实例也可以看出迭代器的设计与容器是相互依存的,迭代器需要知道容器的各种信息(通常包含一个容器对象指针),容器也需要迭代器作为其许多成员函数的接口.
- EOF -
声明:本文采用BY-NC-SA协议进行授权.转载请注明: 仿照SGI-STL实现Hash表
comments powered by