总结在学习Go语言中集合元素-哈希,也就是Map的实现原理过程中的知识点(一)。
哈希表是除数组之外,最常见的数据结构,几乎所有的语言都会有数组和哈希表两种集合元素有的语言将数组实现成列表,有的语言将哈希表称作结构体或者字典,但是它们是两种设计集合元素的思路,数组用于表示元素的序列,而哈希表示的是键值对之间映射关系,只是不同语言的叫法和实现稍微有些不同。
哈希表1是一种古老的数据结构,在 1953 年就有人使用拉链法实现了哈希表,它能够根据键(Key)直接访问内存中的存储位置,也就是说我们能够直接通过键找到该键对应的一个值。它之所以重要不仅仅是因为它O(1)
的读写删除性能优秀,还因为他提供了键值之间的映射。想要实现一个性能优异的哈希表就必须要具备两个因素——完美的哈希函数和冲突解决方法。
开放定址法和拉链法
- 哈希函数介绍、开放定址法和拉链法:文章1
1.开放定址法
从发生冲突的那个单元起,按照一定的次序,从哈希表中找到一个空闲的单元。然后把发生冲突的元素存入到该单元的一种方法。开放定址法需要的表长度要大于等于所需要存放的元素。在开放定址法中解决冲突的方法有:线行探查法、平方探查法、双散列函数探查法。
1.1 线行探查法
线行探查法是开放定址法中最简单的冲突处理方法,它从发生冲突的单元起,依次判断下一个单元是否为空,当达到最后一个单元时,再从表首依次判断。直到碰到空闲的单元或者探查完全部单元为止。复杂度最坏O(n)
1.2 平方探查法
平方探查法即是发生冲突时,用发生冲突的单元d[i], 加上 1²、 2²等。即d[i] + 1²,d[i] + 2², d[i] + 3²…直到找到空闲单元。在实际操作中,平方探查法不能探查到全部剩余的单元。不过在实际应用中,能探查到一半单元也就可以了。若探查到一半单元仍找不到一个空闲单元,表明此散列表太满,应该重新建立。
1.3 双散列函数探查法
这种方法使用两个散列函数hl和h2。其中hl和前面的h一样,以关键字为自变量,产生一个0至m—l之间的数作为散列地址;h2也以关键字为自变量,产生一个l至m—1之间的、并和m互素的数(即m不能被该数整除)作为探查序列的地址增量(即步长),探查序列的步长值是固定值l;对于平方探查法,探查序列的步长值是探查次数i的两倍减l;对于双散列函数探查法,其探查序列的步长值是同一关键字的另一散列函数的值。
2. 拉链法
如文章1中介绍
文章1中和上面 介绍了 开放定址法 和 拉链法,下面来说一下它们之间的优点和缺点
拉链法优点:
①拉链法处理冲突简单,且无堆积现象,因此平均查找长度较短;
②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
④在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结点而将空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。
拉链法缺点和开放定址法优点:
①在拉链法中,指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。
开放定址法缺点:
①开放定址法的缺点在于删除元素的时候不能真的删除,否则会引起查找错误,只能做一个特殊标记。只到有下个元素插入才能真正删除该元素。
拉链法是哈希表中最常见的实现方法,大多数的编程语言都用拉链法实现哈希表。例如在jdk1.8之后采用 链表+红黑树 数据结构来去存取发生哈希冲突的输入域的关键字。
其他几种冲突解决方法
再哈希法
就是同时构造多个不同的哈希函数: Hi = RHi(key) i= 1,2,3 ... k;
当H1 = RH1(key)
发生冲突时,再用H2 = RH2(key)
进行计算,直到冲突不再产生,这种方法不易产生聚集,但是增加了计算时间。
建立公共溢出区
将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。