目录
概念
冲突
如何尽量减少冲突?
负载因子
解决冲突的几种方案
冲突严重时的解决办法
哈希表的实现
基本类型哈希桶实现
泛型哈希桶实现
注意!!!
概念
构造出一种存储结构,通过某种函数使元素的存储位置(下标)与它的关键码之间能够建立一一的映射关系,那么在查找时,通过该函数就可以很快找到该元素
哈希函数中使用的转换函数称为哈希(散列)函数,构造出的结构称为哈希表(散列表)
插入元素
根据待插入元素的关键码,通过哈希函数计算出该元素的存储位置,并按照此位置进行存放
搜素元素
通过哈希函数对元素的关键码进行计算,得到的值就是元素的存储位置,按此位置取出元素,若关键码相等则搜索成功
冲突
不同的关键码通过相同的哈希函数进行计算,可能得到相同的存储位置,这种现象就称为哈希冲突(哈希碰撞),冲突无法避免,我们只能减少冲突,并且再发生冲突时,给出解决方案
如何尽量减少冲突?
1.设计合理的哈希函数
2.调节负载因子
合理的哈希函数设计原则:
1.哈希函数的定义域必须包括需要存储的全部关键码,哈希表允许有n个地址时,其值域必须在0到n-1之间
2.哈希函数计算出来的地址要能均匀分布在整个空间中
3.哈希函数要尽量简单
常见哈希函数
1.直接定址法
取关键码的某个线性函数为散列地址:Hash(Key) = A*Key + B 优点:简单,均匀.缺点:需要事先知道关键码的分布情况和使用场景:适合查找比较小且连续的情况
2.除留余数法
设哈希表中允许的地址数为m,取一个不大于m,但最接近于或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key%p(p<=m),将关键码转换为哈希地址
3.平方取中法
对关键码进行平方,取中间的三位作为哈希地址
4.md5
5.还有折叠法,随机数法,数学分析法等
负载因子
哈希表的载荷因子定义为:α = 填入表中的元素个数/哈希表长度
当我们填入哈希表中的数据越多,发生冲突的概率就越大,我们通常把载荷因子控制在0.7-0.8之间,也就是说一个哈希表最多只能存大小0.7-0.8个哈希表长度的数据,当我们存放的数据超过这么多时,我们需要对哈希表进行扩容,注意!!!哈希表括完容后,我们需要遍历哈希表,重新计算哈希表中的每个元素的地址,因为哈希表长变了.
解决冲突的几种方案
闭散列:也叫开放地址法,当发生哈希冲突时,如果哈希表未被装满,说明哈希表中还有空的位置,那么可以把key放到冲突位置的"下一个"空位置去.如何找到下一个空位置
1.线性探测
从发生冲突的位置开始,依次向后探测,直到寻到下一个空位置为止
缺陷:容易导致发生冲突的元素都集中到一起,且采用闭散列处理哈希冲突时,不能随便物理删除表中已有的元素,若直接删除会影响其它元素的搜索
2.二次探测
线性探测的缺陷是产生冲突的数据堆积在一块,这是因为线性探测在找空位置时,是挨个往后逐渐寻找,二次探测为了避免这个问题,找下一个空位置的方法为:H1 = (H0 + i²)%m,其中H1是下一个空位置的位置,i是发生冲突的次数,m是哈希表的长度
开散列(哈希桶)
开散列法:又叫链地址法(开链法),首先对关键码集合用哈希函数计算哈希地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,每个桶中的元素通过一个单链表连接起来,各链表的表头存储在哈希表中(妙!)
开散列可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜素了
冲突严重时的解决办法
哈希桶可以看作将大集合的搜索问题转换为小集合的搜索问题,如果冲突严重,说明小集合中有很多元素,小集合的搜索性能是不好的,这个时候我们可以将这个小集合搜索问题再进行转换
例如:1.每个桶的背后是另一个哈希表
2.每个桶背后是一颗搜索树,红黑树等
哈希表的实现
基本类型哈希桶实现
import java.util.Arrays;
public class HashBuck {
static class Node{
public int key;
public int val;
public Node next;
public Node(int key,int val){
this.key = key;
this.val = val;
}
}
public Node[] array;
public int useSize;
public HashBuck(){
this.array = new Node[10];
}
public void put(int key,int val){
int index = key%array.length;
//遍历index下标数组,如果有相同的key进行替换
Node cur = array[index];
while(cur!=null){
if(cur.key == key){
cur.val = val;
return;
}else{
cur = cur.next;
}
}
//进行头插法
Node node = new Node(key,val);
node.next = array[index];
array[index] = node;
useSize++;
if(laodFactor()){
resize();
}
}
public int get(int key){
int index = key%array.length;
Node cur = array[index];
while(cur!=null){
if(cur.key == key){
return cur.val;
}else{
cur = cur.next;
}
}
return -1;
}
private void resize(){
Node[] newArray = new Node[2*array.length];
for (int i = 0; i < array.length ; i++) {
Node cur = array[i];
while(cur!=null){
Node curNext = cur.next;
int newIndex = cur.key % newArray.length;
cur.next =newArray[newIndex];
newArray[newIndex] = cur;
cur = curNext;
}
}
array = newArray;
}
private boolean laodFactor(){
return useSize*1.0f/array.length>0.75;
}
}
泛型哈希桶实现
import java.util.Arrays;
//Hash桶实现,泛型
public class HashBuck2<K,V> {
static class Node<K,V> {
public K key;
public V val;
public Node<K, V> next;
public Node(K key, V val) {
this.key = key;
this.val = val;
}
}
public HashBuck2(){
array = new Node[10];
}
public Node<K,V>[] array;
public int useSize;
public void put(K key,V val){
int hash = Math.abs(key.hashCode());
int index = hash%array.length;
Node<K,V> cur = array[index];
while(cur!=null){
if(cur.key.equals(key)){
cur.val = val;
return;
}else {
cur = cur.next;
}
}
Node<K,V> node = new Node<>(key,val);
node.next = array[index];
array[index] = node;
useSize++;
resize();
}
public V get(K key){
int hash = Math.abs(key.hashCode());
int index = hash% array.length;
Node<K,V> cur = array[index];
while (cur!=null){
if(cur.key.equals(key)){
return cur.val;
}
cur = cur.next;
}
return null;
}
private void resize(){
Node<K,V>[] newArray = new Node[2*array.length];
for (int i = 0; i < array.length; i++) {
Node<K,V> cur = array[i];
while (cur!=null){
Node<K,V> curNext = cur.next;
int hash = Math.abs(cur.key.hashCode());
int newIndex = hash%newArray.length;
cur.next = newArray[newIndex];
newArray[newIndex] = cur;
cur = curNext;
}
}
array = newArray;
}
private boolean loadFactor(){
return useSize*1.0f/array.length>0.75;
}
}
注意!!!
这里泛型的话,如果传入的是String类型,hashCode()算出来的值可能是一个负数
注意:当我们要给哈希表中传入自定义类型时,我们需要重写equals方法和hashCode,此时hashCode相当于定位元素在哪个下标,equals方法帮助我们在这个下标的桶中,找到对应的值
俩个对象的hashCode一样,equlas不一定一样,hashCode一样说明俩个对象在同一下标,同一下标中有很多不同的节点(桶),换句话说,如果一样,那么就不会有冲突的存在了
俩个对象的equals一样,hashCode一定一样,equals一样,说明俩个对象是同一个关键码,我们的hashCode就是根据关键码来进行计算的