简介
哈希表本质上是根据关键码值(Key value)而直接进行访问的数据结构,关键码是通过数据本身用某些规则处理找到的一个储存位置。数据将直接通过关键码存入数组,而取出也直接通过关键码取出。这种结构普遍用数组+链表,或数组+二叉树组成。通过哈希表可以大大加快查找数据的速度。就拿数组+链表的结构来说,其本质就是一个装了N个链表的数组,根据某些规则(取址)将数据存入不同的链表中,将本来一个巨大列表的查询量,分摊到各个小的链表中,从而增加查询速度。
哈希表的结构
哈希表的实现有很多方式,本例拿数组+链表例举。通过一个数组存取多个链表,然后将数据根据一些规则存入不同的链表中。当我们要查询某个数据时,可直接根据规则定位该列表,再进行查找。如果有20个链表,那么相较于没有哈希表的数组来说就减轻了19倍的查找压力。
原数据
数组 |
---|
数据1 |
数据2 |
数据3 |
数据4 |
数据5 |
数据6 |
数据7 |
数据8 |
数据9 |
现在将其存入哈希表。准备一个数组,数组里面存入链表,每个链表存入若干个数据。一般会通过某些规则决定存入哪个链表,取出时也会根据这个规则获取,比如取余法。这里采用的是对数据的序号除3,如果小于1就存入第一个链表,大于1小于2则存入第二个链表,大于2则存入第三个链表。具体如下
数组 | 链表头 | 子节点 | 子节点 |
---|---|---|---|
下标0 | 数据1 | 数据2 | 数据3 |
下标1 | 数据4 | 数据5 | 数据6 |
下标2 | 数据7 | 数据8 | 数据9 |
如果在原数组查询数据9,我们可能需要查9次。如果转换成哈希表,或许只需要4次,一次定位链表位置,三次查找具体值。
地址定位(如何选择存入哪个链表?)
其实常用的“定址”的手法有“五种”:
1)直接定址法
很容易理解,key=Value+C;这个“C"是常量。Value+C其实就是一个简单的哈希函数。
2)除法取余法
key=value%C
C为常数
3)数字分析法
这种蛮有意思,比如有一组value1=112233,value2=112633, value3=119033,针对这样的数我们分析数中间两个数比较波动,其他数不变。那么我们取key的值就可以是key1=22,key2=26,key3=90。
4)平方取中法
在关键字的平房里取中间的几个数值作为key
比如:关键字:1234,关键字的平方:1522756,哈希函数值:227
5)折叠法
举个例子,比如value=135790,要求key是2位数的散列值。那么我们将 value变为13+57+90=160,然后去掉高位“1”,此时key=60
哈希冲突
刚刚提到用某些规则决定存入到哪个链表,很多时候这个规则必然会存在一些问题,比如我两个不同的数据序号,却定位到了同一个表上(当然本例没有这种情况,本例也是一种解决冲突的方法),这种情况叫做哈希冲突,哈希冲突不可避免,只能尽量减少。
解决哈希冲突
开放地址法
线性探查再散列
假定取哈希的方式为取余法,有n=1,2,3……,m-1,这个n会不断递增,直到找到合适哈希值为止。
一般形式: 新的哈希值=(冲突的哈希值+n)%取余的值
举个例子:47和69对11取余都等于3,显而易见他们产生了冲突。那么就可以带入公式为69寻找新的哈希值,新的哈希值=(3+1)%11,新的哈希值为4。如果依然冲突,就将n不断递增,直到找到新哈希值,比如5=(3+2)%11。
单链表法
单链表法就是上文所展示的方法,一个哈希值存入一个链表,那么即便有哈希冲突也能存入不同的数据。
结语
并未做专业描述,这里简要介绍了一下哈希表的结构。