赞
踩
散列表使用某种算法操作(散列函数)将键转化为数组的索引来访问数组中的数据,这样可以通过Key-value的方式来访问数据,达到常数级别的存取效率。现在的nosql数据库都是采用key-value的方式来访问存储数据。
散列表是算法在时间和空间上做出权衡的经典例子。通过一个散列函数,将键值key映射到记录的访问地址,达到快速查找的目的。如果没有内存限制,我们可以直接将键作为数组的索引,所有的操作操作只需要一次访问内存就可以完成。但这种情况不太现实。
哈希表优点
哈希表缺点
在初中的数学课本中学习过函数的相关知识,给定一个 x,通过一个数学公式,只需要将 x 的值带入公式就可以求出一个新的值 y。
哈希表的建立同函数类似,把函数中的 x 用查找记录时使用的关键字来代替,然后将关键字的值带入一个精心设计的公式中,就可以求出一个值,用这个值来表示记录存储的哈希地址。即:
数据的哈希地址=f(关键字的值)
…
- 哈希地址只是表示在查找表中的存储位置,而不是实际的物理存储位置。
f()是一个函数,通过这个函数可以快速求出该关键字对应的的数据的哈希地址,称之为“哈希函数”。
例如,这里有一个电话簿(查找表),电话簿中有 4 个人的联系方式:
张三 13912345678
李四 15823457890
王五 13409872338
赵六 13805834722
假如想查找李四的电话号码,对于一般的查找方式最先想到的是从头遍历,一一比较。而如果将电话簿构建成一张哈希表,可以直接通过名字“李四”直接找到电话号码在表中的位置。
在构建哈希表时,最重要的是哈希函数的设计。例如设计电话簿案例中的哈希函数为:每个名字的姓的首字母的 ASCII 值即为对应的电话号码的存储位置。这时会发现,张三和赵六两个关键字的姓的首字母都是 Z ,最终求出的电话号码的存储位置相同,这种现象称为冲突。在设计哈希函数时,要尽量地避免冲突现象的发生。
对于哈希表而言,冲突只能尽可能地少,无法完全避免。
散列函数就是将键转化为数组索引的过程。且这个函数应该易于计算且能够均与分布所有的键。
散列函数最常用的方法是除留余数法。这时候被除数应该选用素数,这样才能保证键值的均匀散布。
散列函数和键的类型有关,每种数据类型都需要相应的散列函数;比如键的类型是整数,那我们可以直接使用除留余数法;这里特别说明下,大多数情况下,键的类型都是字符串,这个时候我们任然可以使用除留余数法,将字符串当做一个特别大的整数。
int hash = 0;
for (int i=0;i<s.length();i++){
hash = (R*hash +s.charAt(i)%M);
}
还有,比如下面的:
Hash hashCode(char *key){
int offset = 5;
Hash hashCode = 0;
while(*key){
hashCode = (hashCode << offset) + *key++;
}
return hashCode;
}
使用时 hashCode(key) & (size-1) 就可以得到一个 size-1 范围内的hash值
当然,还有其他的散列函数,如平方取中法, 随机数法等。
哈希函数的构造:
常用的哈希函数的构造方法有 6 种:直接定址法、数字分析法、平方取中法、折叠法、除留余数法和随机数法。
hash 表有一个特性, 随着元素越来越多, 新插入一个元素发生hash冲突的次数就会越来越多, 查找一个元素的速度也会越来越慢。
不同的关键字得到同一个散列地址f(key1)=f(key2),即为碰撞 。这是我们需要尽量避免的情况。常见的处理方法有:
开放定址法
使用hash table剩下空间。遇到冲突时顺着原先的hash address找到下一个空闲地址然后插入。
这种方法有一个显著问题:空间不足怎么解决?
这个时候可以使用链地址法
H(key)=(H(key)+ d)MOD m(其中 m 为哈希表的表长,d 为一个增量)
当得出的哈希地址产生冲突时,选取以下 3 种方法中的一种获取 d 的值,然后继续计算,直到计算出的哈希地址不在冲突为止,这 3 种方法为:
注释:在线性探测法中,当遇到冲突时,从发生冲突位置起,每次 +1,向右探测,直到有空闲的位置为止;二次探测法中,从发生冲突的位置起,按照 +12,-12,+22,…如此探测,直到有空闲的位置;伪随机探测,每次加上一个随机数,直到探测到空闲位置结束。
链地址法
如果遇到冲突,会在原地址新建一个空间,然后以链表节点的形式插入到该空间。
将大小为M的数组中的每个元素指向一条链表,链表中的每个节点都存储了散列值为该元素索引的键值对。每条链表的平均长度是N/M,N是键值对的总个数。如下图:
哈希函数:
添加操作:
删除操作:
上面如果看不懂,可以继续参看如下示例:
将所有产生冲突的关键字所对应的数据全部存储在同一个线性链表中。例如有一组关键字为{19,14,23,01,68,20,84,27,55,11,10,79},其哈希函数为:H(key)=key MOD 13,使用链地址法所构建的哈希表如图
链地址法的优缺点:
优点:
缺点
指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。
** 再哈希法**
当通过哈希函数求得的哈希地址同其他关键字产生冲突时,使用另一个哈希函数计算,直到冲突不再发生。
建立一个公共溢出区
建立两张表,一张为基本表,另一张为溢出表。基本表存储没有发生冲突的数据,当关键字由哈希函数生成的哈希地址产生冲突时,就将数据填入溢出表。
参考:数据结构与算法之哈希表 HashTable 散列表
哈希表(散列表)及哈希表处理冲突的方法
数据结构与算法(七)-哈希表(HashTable)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。