概念
哈希表是一种高效的数据结构,它的特点是可以不经过任何比较,直接从表中取出想要的元素,这种存储结构是按照某种函数将元素的存储地址和其关键字建立一一映射的关系,查找的时候就可以通过该函数很快的查找到该元素
,同时这个转换函数一般被称为哈希函数 ( f(key) )。
当我们进行插入操作时:
- 根据待插入元素的关键字,通过哈希函数计算出该元素的存放位置,并放入这位置。
当我们进行查找操作时:
- 根据待查找元素的关键字,通过哈希函数计算出该元素的存放地址,在结构中按此位置取元素进行比较,如果关键码相同则查找成功。
冲突
有时,两个数据元素的关键字经过哈希函数的运算后可能得到相同的哈希地址,这种情况就称为哈希碰撞或者哈希冲突。
- 具有不同关键字而具有相同哈希地址的数据元素成为同义词
- 两个相同的关键字得到的哈希地址一定相同
处理冲突
一般来说我们有两种思路来处理哈希冲突
-
避免冲突
通过选择更合适的哈希函数来降低冲突概率(至于选择怎样的哈希函数能降低冲突概率,实际上也是一个数学问题)例如,把数组长度设置成素数,则冲突概率会降低不少。例如给定的数组长度为100,但实际上有80个元素需要存储,那么此时哈希冲突发生的概率就会比较高,如果将数组长度设定为1000,那么冲突的概率就会降低很多。
-
正面应对
- 闭散列:
发现冲突之后,在冲突位置附近找一个空闲位置插入元素(通常分为线性探测,二次探测和更复杂的探测方法) - 开散列:
开散列的数组中每个节点相当于一个链表的头结点,当出现哈希冲突的时候就把新的元素插入到链表中(头插)
无论是采取第一种还是第二种处理方法,但冲突比较严重的时候插入和删除操作都是效率比较低的。
- 闭散列:
如果冲突变得特别严重,那么
- 扩容(降低负载因子,即需要存储的元素个数 / 生成的数组长度),成本比较高,一般不推荐
- 针对开散列来说,把链表替换为更高效的数据结构,比如二叉搜索树(HashSet,HashMap就是这种操作),或者哈希表(二次哈希表)
哈希冲突虽然会影响插入删除效率,但是仍然近似的认为哈希表查找和删除操作的时间复杂度为O(1)。
哈希函数的选择:
如果关键字为整数,则哈希值的计算主要就是通过除留余数的方式,还会搭配一些其他的数学变换公式。
如果关键字为字符串,则会使用 md5、sha1 常用的字符串哈希算法
md5的特点:
- 无论输入的字符串有多长,最终得到的md5都是一个固定长度的值
- 如果两个字符串只有一个字符不同,那么最终得到的md5值也是天壤之别
- 通过字符串计算md5很高效,但是反之则不行
简单哈希表的实现:
public class MyHashMap {
static class Node {
public int key;
public int value;
public Node next;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
//设置为素数降低冲突率
public Node[] array = new Node[101];
private int size = 0;
//负载因子:size / array.length;
//哈希函数
private int hashFunc(int key) {
return key % array.length;
}
//插入函数
public void put(int key, int value) {
//根据key带入到哈希函数中,计算出存放位置
int index = hashFunc(key);
//先找到此位置的头结点
Node head = array[index];
//先判断key是否存在,如果存在就修改value,不插入新节点
for (Node cur = head; cur != null; cur = cur.next) {
if (cur.key == key) {
cur.value = value;
return;
}
}
Node node = new Node(key, value);
node.next = head;
array[index] = node;
size++;
}
//寻找元素
public Integer get(int key) {
//1.根据key查找到value;
int index = hashFunc(key);
//2.根据index得到相应位置的链表
Node head = array[index];
//在链表中寻找
for (Node cur = head; cur != cur.next; cur = cur.next) {
//如果找到了
if (cur.key == key) {
return cur.value;
}
}
//如果没找到
return null;
}
}