目录
1哈希表的概念:
![](https://img-blog.csdnimg.cn/c21224f2547d4a9fb484fea092f8f294.png)
1.1哈希表的插入图示:
1.2哈希表的查询图示:
上图解释了如何以哈希的方式存储数据了;那么数据该如何读取呢?请看下图!
2.哈希冲突
2.1哈希冲突的概念:
2.2避免冲突
哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这也就导致了一个问题,那就是冲突是必然会发生;虽然冲突是必然发生的;但是我们能做到的是尽量降低冲突的概率.
如何有效避免哈希冲突呢?从以下两个方面入手:哈希函数的设计,负载因子的调节!
2.2.1哈希函数设计
哈希函数设计不合理是引起哈希冲突的一个重要原因;那么常见的哈希函数主要是以下两种!
1. 直接定制法:
2. 除留余数法:
2.2.2负载因子的调节
负载因子的定义: a = 填入表中的元素个数 / 哈希表的长度
通俗的理解就是:当哈希表长度一定时;随着插入表中元素的增加;负载因子(a)就会增大;而负载因子越大,冲突产生的概率就越大;所以我们可以通过降低负载因子来降低冲突率.
负载因子与冲突率的关系如下:
那么怎么调节负载因子呢?
由定义公式可知:a = 填入表中的元素个数 / 哈希表的长度;
我们可以通过增大分母来使负载因子a降低;也就是将哈希表的长度增长,通俗的讲:将数组扩容;
3.解决冲突
解决哈希冲突两种常见的方法是:闭散列和开散列
3.1闭散列:
![](https://img-blog.csdnimg.cn/386fe26d1d034fa59d4414387d4e9c34.png)
虽然是线性探测解决了寻找空位的问题,但是又引来了另一个问题:那就是会使产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨 着往后逐个去找,因此二次探测为了避免该问题.
2.二次探测:找下一个空位置的方法为:H(i) = (H0 + i^2 ) % m, 或者:H(i)= (H0 - i^2) % m。其中:i = 1,2,3…, 是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。(通俗的理解就是:虽然也是找空位插入,但是在找空位的时候,跟上一个找的位置隔的更远了,也就使的冲突的数据分的更加开了)
3.2开散列/(哈希桶)
![](https://img-blog.csdnimg.cn/b8e6c34aa15749d7ac8750d70c61a422.png)
4.哈希表的实现
public class HashBucket {
static class Node {
public int key;
public int val;
public Node next;
public Node(int key, int val) {
this.key = key;
this.val = val;
this.next = null;
}
}
private Node[] array;
public int usedsize;
public HashBucket() {
this.array = new Node[10];
this.usedsize = 0;
}
public void put(int key, int val) {
//1.确定下标==哈希函数
int index = key % this.array.length;
//2.遍历这个下标的链表
Node cur = array[index];
//头插法
while (cur != null) {
//如果key已经存在 更新val
if (cur.key == key) {
cur.val = val;
return;
}
cur = cur.next;
}
//3.cur == null 当前数组下标下的链表 没有key 则将其以头插法进行插入
Node node = new Node(key, val);
node.next = array[index];
array[index] = node;
this.usedsize++;
//4.判断是否需要扩容
if (loadFactor() >= 0.75) {
//扩容
resize();
}
}
public double loadFactor() {
return this.usedsize * 1.0 / this.array.length;
}
public void resize() {
//自己创建新的数组 该数组是原本数组长度的两倍
Node[] newArray = new Node[2 * this.array.length];
//需要把原本数组里所存储的数据全部重新进行哈希 因为数组的长度变了 哈希函数里的数值就变了 所以需要重新哈希
//遍历原本的哈希桶
//最外层循环 控制数组的下标
for (int i = 0; i < array.length; i++) {
Node cur = array[i];
Node curNext = null;
while (cur != null) {
//记录cur.next
curNext = cur.next;
//重新确立新的下标 然后进行头插法
int index = cur.key % newArray.length;
cur.next = newArray[index];
newArray[index] = cur;
cur = curNext;
}
}
this.array = newArray;
}
public int get(int key) {
//以什么样的方式存储的 就以什么样的方式取出
int index = key % this.array.length;
Node cur = array[index];
while (cur != null) {
if (cur.key == key) {
return cur.val;
}
cur = cur.next;
}
return -1;
}
}