哈希表
1. 哈希表的定义
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。key(键值)和存储地址的映射
可以把哈希表理解为一个数组,每个索引对应一个存储位置,哈希表的索引并不像普通数组的索引那样,从0到length-1,而是由关键字(数据本身)通过哈希函数得到。而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。
2.哈希冲突
即不同key值产生相同的地址,在公式上表达就是key1≠key2,但f(key1)=f(key2),即不同的key值,通过哈希函数转化后得到的存储地址是一样的。
解决哈希冲突:
1.开放定址法
如果遇到冲突的时候怎么办呢?就找hash表剩下空余的空间,找到空余的空间然后插入。就像你去商店买东西,发现东西卖光了,怎么办呢?找下一家有东西卖的商家买呗。
由于我没有深入试验过,所以贴上在书上的解释:
2.链地址法
上面所说的开放定址法的原理是遇到冲突的时候查找顺着原来哈希地址查找下一个空闲地址然后插入,但是也有一个问题就是如果空间不足,那他无法处理冲突也无法插入数据,因此需要装填因子(插入数据/空间)<=1。
链地址法的原理时如果遇到冲突,他就会在原地址新建一个空间,然后以链表结点的形式插入到该空间。
比如说我有一堆数据{1,12,26,337,353…},而我的哈希算法是H(key)=key mod 16,第一个数据1的哈希值f(1)=1,插入到1结点的后面,第二个数据12的哈希值f(12)=12,插入到12结点,第三个数据26的哈希值f(26)=10,插入到10结点后面,第4个数据337,计算得到哈希值是1,遇到冲突,但是依然只需要找到该1结点的最后链结点插入即可,同理353。
3.构造哈希表
一般用对象数组和链表来组成哈希表,通过链表来解决哈希冲突。
数组的特点是:寻址容易,插入和删除困难;
而链表的特点是:寻址困难,插入和删除容易。
那么能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,下面采用最常用的一种方法——拉链法,我们可以理解为“链表的数组”,如图:
左边很明显是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。
代码实现:
- 创建hash表,管理多条链表。
- 创建哈希函数。
- 根据散列函数确定关键字应该放在哪条链表上面,调用链表类的增加方法。
- 查询时,根据哈希函数得到其存储位置,然后遍历此位置的链表进行查找。
- 创建实体类,即链表的类型。
- 创建链表类,封装增删改查方法。
//创建hash表,管理多条链表
class HashTable {
private EmpLinkedList[] arr;
private int size;
// 初始化构造器
public HashTable(int size) {
// 创建大小为7的数组
arr = new EmpLinkedList[size];
this.size = size;
// 除了初始化数组,还需要初始化链表
for (int i = 0; i < size; i++) {
arr[i] = new EmpLinkedList();
}
}
// 添加员工
public void add(Emp emp) {
// 根据散列函数确定员工编号应该放在哪条链表上面
int hashFun = hashFun