有一种数据结构,叫作散列表,还有一些称之为“字典”(dict)、“映射”(map)、“哈希”(hash)。数组
这种数据结构有个特色,通常状况下,能在O(1)时间内根据关键字找到要查询的信息(进行一次或者不多次比较),这是由于散列表的底层通常会使用数组实现,利用“散列函数”或者称为“hash函数”,能够计算出该元素应该存到哪一个位置,可以让全部的元素均匀分布在数组中,下一次查找的时候,根据散列函数计算出对应的位置,就能找到元素。数据结构
可是存在一个问题,没有完美的hash函数!!!!也就是说目前不能肯定某个元素的位置,且不重复!也就是说,散列可能会存在冲突,好比某个元素经过散列函数计算,肯定应该存在下标5的位置,下一次再来一个元素,计算后发现仍是存在下标为5的位置,此时就出现了冲突。函数
出现hash冲突后,有多种方式能够解决冲突,好比开放地址法、链地址法(拉链法),本文主要介绍开放地址法的一种——线性探测法。测试
根据维基百科对线性探测法的介绍,摘抄以下:spa
线性探测是一种开放寻址的策略。在这些策略里,散列表的每一个单元都存储一对键值对。当散列函数对一个给定值产生一个键,而且这个键指向散列表中某个已经被另外一个键值对所占用的单元时,线性探测用于解决此时产生的冲突:查找散列表中离冲突单元最近的空闲单元,而且把新的键插入这个空闲单元。一样的,查找也同插入一模一样:从散列函数给出的散列值对应的单元开始查找,直到找到与键对应的值或者是找到空单元。code
讲的通俗一点,就是发现蹲坑的时候发现坑已经被占了,就找后面一个坑,若是后面一个坑空闲,则占用这个空闲的坑;若是后面一个坑也被占了,则一直日后面的坑进行遍历,直到找到空闲的坑,不然就一直憋着。blog
目前有一个长度为8的数组,选择的hash函数是 e.key%8,这个8是指数组的长度(容量),当数组长度发生变化,hash函数也应该变化。hash
新加入一个元素e1,key为5,hash函数计算出应该存到位置5,由于5%8=5,而后看位置5有没有被占用,发现没有被占用,则将e1存入位置5:it
又加入一个新元素e2,key为13,hash函数计算出也应该存到位置5,由于13%8=5,可是发现位置5已经被占用了,此时就位置6有没有被占用,此时发现位置6没有被占用,则e2就存到位置6了class
此时又来一个e3,key为21,发现还要存到位置5,可是位置5已经被占了,日后看位置6,发现位置6也被占了,再看位置7,位置7空着,因此e3就存到位置7了
此时来了一个e4,key为29,发现仍是存到位置4,而且位置五、六、7已经都被占用了,此时只能从头考虑位置0了,发现位置0未被占用,则将e4存到位置0
因此说,上面的数组实际上是一个环形数组。
如今要查询key为5的元素,经过计算,对应的位置为5,查看位置5的key,发现位置5的key与要查找的key相等,则查找成功,返回e1;
若是要查询一个key为13的元素,经过计算key为13,对应位置5,可是位置5的key为5,与13不匹配,此时日后看位置6,发现位置6的key为13,与要查找的key相等,此时查找成功,返回e2便可;
若是要查询key为37的元素,经过计算对应位置5,可是位置5的key为5,与13不匹配,日后看位置6的key为13也和37不匹配,一直到位置0,发现e4的key为29,仍旧不匹配要找的37,接着看位置1,发现位置1没有元素,证实数组中没有存key为37的元素,查找失败。
以上面插入e4到位置0后进行介绍
此时要删除一个元素key为13,此时,经过hash计算出位置应该在位置5,由于13%8=5,发现位置5的key为5,不等于要删除的13;
日后看位置6,发现位置6的key为13,和要删除的key相同,测试将位置6的元素删除:
若是此时不进行其余修改操做,而是进行查找操做,好比查找key为21的元素,应该对应位置5,可是位置5已经有元素了,且不是要找的元素,此时会日后看下一个位置,发现位置6为空,没有元素,因此这次查询失败!!!
可是,此次查询是失败的!不是说查询的方式有问题,而是说数组的元素存放有问题,由于key为21的元素在数组中,可是却并无被查询出来。
为了解决这个问题,咱们在删除元素后,要将其后面的元素进行从新肯定位置,也就是rehash,过程以下:
删除位置6的元素,因此看位置6后面的元素,7->0->5,每一个元素都须要计算hash,肯定新位置。
好比位置7的key为21,发现应该调整到位置5,发现位置5已经有了元素,看位置6,发现位置6空着,则将元素e3放入位置6
接着看位置0的元素是否须要调整,在进行计算而且通过上面的流程后,e4应该调整到位置7
须要注意的是,调整位置0后,因为位置1没有元素,则能够中止调整,由于没有元素,则表示后面的第一个非空位置存的元素(好比e1)确定没有冲突。
当数组中全部位置都填满了,此时再插入元素,就无处安放了,此时有两种作法:1.拒绝插入;2.扩容。
通常来讲,并非当数组没有空位时才扩容,而是数组元素达到必定阈值后就进行扩容,可是须要注意的是数组扩容要作的不仅是数组扩容,还须要将旧数组中的元素拷贝到新数组中。
当数组扩容后(假设是翻倍),则数组长度变为16,下标从0~15,以下图所示:
在拷贝的过程当中,须要从新计算每一个元素的hash值,也就是肯定每一个元素在新数组中的位置,其实从左往右遍历旧数组当中的元素,依次插入到新数组中,有冲突就按照原来的方式解决冲突便可。
扩容后,新的散列函数为e.key%16:
首先是看位置0,有元素,元素e4的key为29,则新位置为13,由于29%16=13,发现新数组的位置13空着,因而e4元素就放入位置13;
而后从左往右遍历到e1,发现元素e1的key为5,5%16=5,因此新位置为5,恰好位置5也空着,因此e1放入位置5;
而后轮到e2,计算的key应该保存到13,13%16=13,应该放到位置13上,可是位置13上已经有了元素,日后看位置14,发现位置14空着,因而e2就放入位置14
.....全部元素都完成拷贝后,数组的扩容才真的完成,以下图所示: