Hash表(Hash Table)

 

hash表实际上由size个的桶组成一个桶数组table[0...size-1] 。

当一个对象经过哈希之后。得到一个对应的value , 于是我们把这个对象放到桶table[ value ]中。当一个桶中有多个对象时。我们把桶中的对象组织成为一个链表。

这在冲突处理上称之为拉链法。

负载因子(load factor)

 

如果一个hash表中桶的个数为 size , 存储的元素个数为used .则我们称 used / size 为负载因子loadFactor . 一般的情况下,当loadFactor<=1时,hash表查找的期望复杂度为O(1). 因此。每次往hash表中加入元素时。我们必须保证是在loadFactor <1的情况下,才可以加入。

容量扩张(Expand)& 分摊转移

 

当我们加入一个新元素时。一旦loadFactor大于等于1了,我们不能单纯的往hash表里边加入元素。

由于加入完之后,loadFactor将大于1,这样也就不能保证查找的期望时间复杂度为常数级了。这时。我们应该对桶数组进行一次容量扩张,让size增大 。

这样就能保证加入元素后 used / size 仍然小于等于1 , 从而保证查找的期望时间复杂度为O(1).可是。怎样进行容量扩张呢? C++中的vector的容量扩张是一种好方法。

于是有了例如以下思路 : Hash表中每次发现loadFactor==1时,就开辟一个原来桶数组的两倍空间(称为新桶数组),然后把原来的桶数组中元素所有转移过来到新的桶数组中。注意这里转移是须要元素一个个又一次哈希到新桶中的。原因后面会讲到。

这样的方法的缺点是,容量扩张是一次完毕的,期间要花非常长时间一次转移hash表中的全部元素。这样在hash表中loadFactor==1时。往里边插入一个元素将会等候非常长的时间。

redis中的dict.c中的设计思路是用两个hash表来进行进行扩容和转移的工作:当从第一个hash表的loadFactor=1时,假设要往字典里插入一个元素。首先为第二个hash表开辟2倍第一个hash表的容量。同一时候将第一个hash表的一个非空桶中元素所有转移到第二个hash表中。然后把待插入元素存储到第二个hash表里。继续往字典里插入第二个元素,又会将第一个hash表的一个非空桶中元素所有转移到第二个hash表中,然后把元素存储到第二个hash表里……直到第一个hash表为空。

这样的策略就把第一个hash表全部元素的转移分摊为多次转移,并且每次转移的期望时间复杂度为O(1)。

这样就不会出现某一次往字典中插入元素要等候非常长时间的情况了。

为了更深入的理解这个过程。先看看在dict.h中的两个结构体:

typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int iterators; /* number of iterators currently running */
} dict;

dictht指的就是上面说的桶数组,size用来表示容量,一般为2^n ,sizemask(一般为2^n-1,二进制表示为n个1)用来对哈希值取模 , used表示hash表中存储了多少个元素。

dict表示字典,由两个桶数组组成。type是一些函数指针(哈希函数及key。value的一些处理函数)。

d->rehashidx

 

这个变量的理解非常关键:

d->rehashidx 表明了新元素究竟是存储到桶数组0中。还是桶数组1中,同一时候指明了d->h[0]中究竟是哪一个桶转移到d->h[1]中。

当d->rehashidx==-1时,这时新加入的元素应该存储在桶数组0里边。

当d->rehashidx!=-1 时,表示应该将桶数组0中的第一个非空桶元素所有转移到桶数组1中来(最好还是称这个过程为桶转移,或者称为rehash)。这个过程必须将非空桶中的元素一个个又一次哈希放到桶数组1中,由于d->h[1]->sizemask已经不同于d->h[0]->sizemask了。

这时新加入的元素应该存储在桶数组1里边,由于此刻的桶数组0的loadFactor为1 。而桶数组1的loadFactor小于1 。

当发现桶数组0中的元素所有都转移到桶数组1中,即桶数组0为空时。释放桶数组0的空间。把桶数组0的指针指向桶数组1。将d->rehashidx赋值为-1 ,这样桶数组1就空了,下次加入元素时。仍然加入到桶数组0中。直到桶数组0的元素个数超过桶的个数,我们又又一次开辟桶数组0的2倍空间给桶数组1 ,同一时候改动d->rehashidx=0。这样下次加入元素是就加入到桶数组1中去了。

值得注意的是。在每次删除、查找、替换操作进行之前,依据d->rehashidx的状态来推断是否须要进行桶转移。这能够加快转移速度。

以下是一份精简的伪代码,通过依次插入element[1..n]这n个元素到dict来具体描写叙述容量扩张及转移的过程:

//初始化两个hash表
d->h[0].size = 4 ; d->h[1].used = 0 ;  //分配四个空桶
d->h[1].size = 0 ; d->h[1].used = 0 ;  //初始化一个空表
 
for(i = 1 ; i <= n ; ++ i){
      if( d->rehashidx !=-1 ){
                  if(d->h[0]->used != 0){
                            把 d->h[0]中一个非空桶元素转移(又一次hash)到 d->h[1]中  。  
                            // 上一步会使得:
                            // d->h[0]->used -= 转移的元素个数 
                            // d->h[1]->used += 转移的元素个数 。
                            把 element[i] 哈希到 d->h[1]中  ;  // d->h[1]->used ++ 
                  }else{
                            //用桶数组1覆盖桶数组0; 赋值前要释放d->h[0]的空间,赋值后重置d->h[1])
                            d->h[0] = d->h[1] ; 
                            d->rehashidx = -1 ; 
                            把element[i]哈希到d->h[0]中;// d->h[0]->used ++ ; 
                 }
      }else if( d->h[0]->used >= d->h[0]->size )
                d->h[1] = new bucket[2*d->h[0]->size ];    
                // d->h[0]->size 等于d->h[0]->size的2倍 
                把element[i]哈希到d->h[1]中 ;  // d->h[1]->used ++ 
                d->rehashidx = 0 ;                             
      }else{
                把element[i]哈希到d->h[0]中;  // d->h[0]->used ++ 
      }
}

字典的迭代器(Iterator)

分为安全迭代器( safe Iterator )和非安全迭代器

安全迭代器可以保证在迭代器未释放之前,字典两个hash表之间不会进行桶转移。

桶转移对迭代器的影响是很大的,如果一个迭代器指向d->h[0]的某个桶中的元素实体。在一次桶转移后,这个实体被rehash到d->h[1]中。

而在d->h[1]中根本不知道哪些元素被迭代器放过过,哪些没有被訪问过,这样有可能让迭代器反复訪问或者缺失訪问字典中的一些元素。

所以安全迭代器可以保证不多不少不反复的訪问到全部的元素(当然在迭代过程中。不能涉及插入新元素和删除新元素的操作)。

本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5245064.html,如需转载请自行联系原作者

Hash表的扩容(转载)相关推荐

  1. 使用两次Hash的Hash表——Twice_Hash_Map

    先回顾一下hash表的相关内容,STL里面的unordered_map和map. 使用unordered_map,通过hash函数,将key映射到一个位置,如果这个位置原本没有值,那么就可以将这个ke ...

  2. 从头到尾彻底解析Hash表算法

    从头到尾彻底解析Hash表算法 发布时间: 2013-10-02 10:26  阅读: 25156 次  推荐: 14   原文链接   [收藏]   作者:July.wuliming.pkuoliv ...

  3. 一步一步写算法(之hash表)

    [ 声明:版权全部,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] hash表,有时候也被称为散列表.个人觉得,hash表是介于链表和二叉树之间的一种中间结构.链 ...

  4. 转 从头到尾彻底解析Hash表算法

    出处:http://blog.csdn.net/v_JULY_v.   说明:本文分为三部分内容,     第一部分为一道百度面试题Top K算法的详解:第二部分为关于Hash表算法的详细阐述:第三部 ...

  5. hash表建立 很久没写数据结构了

    /** auhtor:lx date 4.9 2011 brief hash table */ #include <stdio.h> #include <stdlib.h> # ...

  6. 海量路由表能够使用HASH表存储吗-HASH查找和TRIE树查找

    千万别! 非常多人这样说,也包括我. Linux内核早就把HASH路由表去掉了.如今就仅仅剩下TRIE了,只是我还是希望就这两种数据结构展开一些形而上的讨论. 1.hash和trie/radix ha ...

  7. NOIp 数据结构专题总结 (1):STL、堆、并查集、ST表、Hash表

    系列索引: NOIp 数据结构专题总结 (1) NOIp 数据结构专题总结 (2) STL structure std::vector #include <vector> std::vec ...

  8. luogu4407 [JSOI2009]电子字典 字符串hash + hash表

    暴力枚举,然后\(hash\)表判断 复杂度\(O(26 * 20 * n)\) 具体而言 对于操作1:暴力枚举删除 对于操作2:暴力添加,注意添加不要重复 对于操作3:暴力替换,同样的注意不要重复 ...

  9. POJ-1840 Eqs Hash表

    不得不说上次看得的这句话是多么对,再差的Hash表都比map好,hash表的查找速度可不是logn能够比的. 首先将5个部分拆成2+3,我们选取2的部分进行hash,然后再进行3重for循环. 动态申 ...

最新文章

  1. asp.net考前复习——信息服务身份验证
  2. 实习就参与“服务过亿用户的项目”,是什么体验?
  3. 怎么在电脑上使用python3.6_Windows下 Python3.6.1 运行环境的搭建
  4. eclipse preference没有server_Java Web开发的前期准备工作,部署Tomcat服务器和Server环境创建...
  5. linux fdisk,df,mount挂载及查看磁盘信息
  6. RLS实现求解最小二乘确定性正则方程
  7. sipp工具的使用--简单的呼叫测试
  8. mysql rownum写法_mysql类似oracle rownum写法
  9. JDK和CGLIB动态代理的区别
  10. django系列9 --- 迁移相关
  11. Android ViewGroup中addView方法使用
  12. 第14章 Proxmox VE桌面虚拟化或桌面云
  13. 计算机装系统找不到硬盘,电脑重装系统找不到硬盘驱动器怎么办?
  14. c语言电脑三点竖怎么输入,竖怎么打 【处理步骤】
  15. 临床执业助理医师(综合练习)题库【2】
  16. pocketsphinx 模型库_pocketSphinx 嵌入式关键词唤醒
  17. 大数据基础--学好大数据必看的文章
  18. 神策数据王乾:微信生态与小程序发展趋势洞察
  19. 常见的地理坐标系与投影坐标系
  20. 编程计算1 * 2 * 3+3 * 4 * 5+5 * 6 * 7+...+99 * 100 * 101的值。

热门文章

  1. gitmaven命令
  2. Android入门(12)| 数据持久化
  3. 数据结构 | B树、B+树、B*树
  4. leetcode184. 部门工资最高的员工(SQL) 连接+嵌套查询
  5. 《Head First设计模式》第七章-适配器模式、外观模式
  6. 如何使用github中的pull request功能?
  7. C语言深度剖析书籍学习记录 第七章 文件结构
  8. 健康丨汗从哪里出 病从哪里来
  9. JVM的几点性能优化
  10. FastJson 中 jsonArray 转换成 list 集合的方法