一、哈希表
哈希表是一个典型的用空间换时间的操作,利用数组随机访问的特性,最大化查找效率。哈希过程就是将数组元素与下标建立关系的过程。
二、哈希函数
1、哈希函数的意义:
哈希表是希望将元素与下标建立关系,那么当多个元素之间跨度较大时,我们不可能浪费大量的空间来完成这个存储的过程,就需要通过哈希函数。将下标控制在一定的范围内,就是将我们要存储的键值中的“键”利用特定的哈希函数计算得到一个整型作为数组的索引。
2、哈希函数的设计
(1)当key为整型时
当我们的“键”是一个整型的时候,就利用直接取模的方法,通常情况下会选择素数。若是碰到负整数的时候我们就需要先取绝对值再进行取模。
(2)哈希函数设计的三个要求
一致性:哈希表中的key值是唯一的,所以我们就需要保证相同的key值通过哈希函数求出来的哈希值必须也是相同的
高效性:哈希函数的运算不能太耗时,就像小数取模就是一种高效的方式
均匀性:不同的key值经过哈希函数的运算后尽量避免“冲突”
(3)当key为其他类型时
当我们的key值为其他类型的数据时,我们的思路就是想办法把它变成整型。
key是浮点数:计算机本身就是用整型来模拟浮点数的
key是字符:存储过程中按照编码规则转为整型
key是字符串:按进制转换
“166” = 1*10^2 + 6*10^1 + 6 *10^0
“code” = 99 *26^3 + 111*26^2 + 100*26^1 + 101*26^0
字符串转为整型有一个公式就是:拆开后的各个字符转成整型后乘以进制的当前字符位数的次方后相加
例如166就是10进制,code我们就用的是26个字母26进制
三、哈希冲突
1、哈希冲突的含义
不同的key值进过哈希函数的运算后求得的哈希值相同,这就是哈希冲突,我们在设计的过程中需要尽量避免这种情况,这种情况的存在会导致我们利用哈希表查询元素的时候会增加查找次数等。
2、哈希冲突的解决
(1)闭散列:【线性探测法】
当我们遇到哈希冲突的时候,从当前位置的下一个开始找空位,碰到空位存进去。这种方法好存放但是难查找更加难删除
(2)开散列:【拉链法;链地址法】
当我们遇到哈希冲突的时候,将当前位置的元素变成一个链表或者树
3、哈希冲突较严重时
(1)拓容:将原来的数组拓容为更大的数组,再将元素重新取模放入新的数组中
(2)当链表长度到达6的时候,将链表变成树
四、负载因子
负载因子使用来描述一个哈希表的拥挤程度的。
负载因子 = 元素个数 / 数组长度
我们也可以利用负载因子来动态判断哈希表是否需要扩容,若元素个数大于等于负载因子与数组长度的乘积时就需要进行拓容。
负载因子越大,冲突的几率越大,查找效率越低
负载因子越小,冲突的几率越小,空间浪费越多
五、哈希表的实现
这里我们实现一个key值为整型的数组,选用取模的方式对key值进行处理。
public class HashMapByLink {
class Node{
int key;
int val;
Node next;
public Node(int key, int val, Node next) {
this.key = key;
this.val = val;
this.next = next;
}
}
//取模数
private int M;
//数组的长度
private int size;
//默认的数组长度
private static final int DEFAULT_CAPACITY = 16;
//默认的负载因子
private static final double LOAD_FACTOR = 0.75;
//存储Node结点的数组
private Node[] table;
//创建默认长度的数组
public<