哈希表以及哈希冲突的解决
1.哈希表
1.1概念
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应关系,因此在查找一个元素的时候,必须要经过关键码的多次比较,这样查找的效率就比较低下,搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次字节从表中得到要搜索的元素,如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数很快找到该元素,这种存储结构称为哈希表。
1.1.1插入元素
根据待插入元素的关键码,一次函数计算出该元素的存储位置并按此位置进行存放
1.1.2搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
2.哈希冲突
2.1概念
对于两个数据元素的关键字:key1 和 key2,key1不等于key2,但是Hash(key1)==Hash(key2),即:不同关键字通过相同的哈希函数设计计算出相同的哈希地址,该这种现象称为哈希冲突或哈希碰撞。
把具有不同关键码而具有相同哈希地址的元素称为“同义词”。
2.2避免哈希冲突
由于我们的哈希表底层数组容量往往是小于实际要存储的数据的,这样就导致了冲突的发生是必然的,但是我们能做的是尽量的降低冲突率。
2.3哈希函数的设计
2.3.1哈希函数设计原则
- 哈希函数的定义域必须包括需要存储的全部关键码,如果散列表允许有m个地址时,其值域必须在0到m-1之间
- 哈希函数计算出来的地址能均匀分布在整个空间中
- 哈希函数应该比较简单
2.3.2常见哈希函数
- 直接定制法:取关键字的某个线性函数为散列地址:Hash(key)=A*key+B
- 除留余数法:设散列表中允许的地址数为m,取有一个不大于m,但接近于m的质数p作为除数,按照哈希函数:Hash(key)=key%p(p<=m),将关键码转换成哈希地址
2.4负载因子调节
散列表的载荷因子定义为 α= 填入表中的元素个数/散列表的长度。
α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,表元素越多,产生冲突的可能性就越大;反之,α越小,填入表中的元素越少,产生冲突的可能性就越小。一般调节负载调节因子控制在0.75.左右。
2.5哈希冲的解决方式
2.5.1闭散列
闭散列,也叫开放定址法,当发生哈希冲突时,如果哈希表未被填满,说明表中必然还有空位置,那么可以把key存放到冲突位置的“下一个”空位置中去。
- 线性探测寻找空位置:从冲突位置开始,依次向后试探,直到寻找到下一个空位置为止。
2.5.2开散列
开散列法又叫链地址法,首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一个子集,每个子集称为一个桶,每个桶中的元素通过一个单链表接起来,各链表的头结点存放在哈希表中。
代码实现使用hash桶方式解决hash冲突
import jdk.nashorn.internal.ir.CallNode;
import java.util.HashMap;
class HashNode{
int key;
int val;
HashNode next;
public HashNode(int key, int val) {
this.val=val;
this.key = key;
}
}
public class MyHashMqp {
//hash表的核心是一个数组,数组上的每个元素又是一个链表
private HashNode[] array = new HashNode[16];
private int size = 0;
//通过这个方法。把key映射成数组下标
private int hashCode(int key) {
//此处通过简单求余作为hash函数
return key % array.length;
}
//核心方法
public void put(int key, int value) {
int index = hashCode(key);
for (HashNode cur = array[index]; cur != null; cur = cur.next) {
if (cur.key == key) {
cur.val = value;
return;
}
}
HashNode newNode = new HashNode(key, value);
newNode.next = array[index];
array[index]=newNode;
size++;
if (loadFactor() >=0.75) {
resize();
}
}
public void resize() {
HashNode[] newArray = new HashNode[array.length * 2];
for (int i = 0; i < array.length; i++) {
HashNode next = null;
for (HashNode cur = array[i]; cur != null; cur = cur.next) {
next = cur.next;
int newIndex = cur.key % newArray.length;
cur.next = newArray[newIndex];
newArray[newIndex] = cur;
}
}
array = newArray;
}
private double loadFactor() {
return (double) size / array.length;
}
public Integer get(int key) {
int index = hashCode(key);
for (HashNode cur = array[index]; cur != null; cur = cur.next) {
if (cur.key == key) {
return cur.val;
}
}
return null;
}
public void remove(int key) {
int index = hashCode(key);
if (array[index] == null) {
return;
}
HashNode list = array[index];
if (list.key == key) {
array[index] = array[index].next;
}
for (HashNode cur = list.next; cur != null; cur = cur.next) {
if (cur.key == key) {
list.next = cur.next;
}
list = list.next;
}
}
public static void main(String[] args) {
HashMap<Integer, Integer> hashMap = new HashMap<>();
hashMap.put(2, 1);
hashMap.remove(1);
}
}