1、哈希表简介:
通常用于海量数据处理:1 查重 ;2 求 TOP k (时间复杂度,空间复杂度O(1))
优点:增、删、查找O(1)
缺点:占内存空间较大
应用:HashMap,HashTable,LinkedHashSet…等容器,底层都用了哈希表。因为哈希表的增删查的特性,使这些容器的效率增加。
2、哈希值:
说到哈希值那就要提到哈希函数。因为哈希值并不是固定不变的,而使根据应用的不同的哈希函数而改变,比如HashMap,HashTable底层哈希值的计算用的就不是一个方法。
HashTable是key.hashCode取余;而HashMap都是先对hashCode进行再哈希,然后再与(桶数 - 1)进行取余运算。
一些常见的哈希函数:平方取中、随机数、折叠、数字分析、直接定址、平方取中、随机数、折叠、除留余数;
这里简单的介绍两个常用的哈希函数:
除留余数法 (data%bucket_number=bucket_index)
直接定址法( H(key)=a*key+b (a,b为常数))
3、哈希冲突:
虽然哈希值有很多计算的方法,但是当存储的数字足够多的时候,难免会产生不同的对象拥有相同哈希值的情况。我们把这种情况就叫做哈希冲突。那么解决哈希冲突的方法有很多,这里主要介绍两种:
1、线性探测法(向后找,看有没有空的地方,若有则插入)
插入(线性探测法) 参数:laod factor装载因子(已占用桶的个数/桶的总数)
2、链地址法、拉链法(组织在链表的数据结构上)
4、实现简易的哈希表:
1、插入操作:
首先判断是否需要扩容,及是否大于装载因子;
然后用除留余数发计算出哈希值后判断党建准备插入位置的状态,使used,using,unused,如果此位置正在使用,且当前的元素和待插入元素一样,就返回,避免插入相同值。如过不一样但哈希值一样,那就将当前的idx加一再进行除留取余。
代码实现:
class LinerHashMap<T extends Comparable<T>>{
// 散列表数组
private Entry<T>[] hashTable;
// 被占用的桶的个数
private int usedBucketNum;
// 哈希表的装载因子
private double loadFactor;
// 定义素数表
private static int[] primTable;
// 记录当前使用的素数的下标
private int primIndex;
// 类的静态初始化块
static{
primTable = new int[]{3, 7, 23, 47, 97, 127};
}
/**
* 构造函数,初始化
*/
public LinerHashMap(){
this.primIndex = 0;
this.hashTable = new Entry[primTable[this.primIndex]];
this.usedBucketNum = 0;
this.loadFactor = 0.75;
}
public void put(T key){
//计算哈希表是否需要扩容
double ret = this.usedBucketNum*1.0 / this.hashTable.length;//装载因子=已使用桶数/桶长
if(ret > this.loadFactor){
resize(); // 哈希表的扩容
}
// 先计算key应该放的桶的下标
int index = key.hashCode() % this.hashTable.length;//除留取余法
int idx = index;
do{
// 表示是从未使用过的桶
if(this.hashTable[idx] == null){
this.hashTable[idx] = new Entry<>(key, State.USING);
this.usedBucketNum++;
return;
}
// 表示使用过的桶
if(this.hashTable[idx].getState() == State.USED){
this.hashTable[idx].setData(key);
this.hashTable[idx].setState(State.USING);
this.usedBucketNum++;
return;
} else {
// 正在使用中的桶,不插入重复元素
if(this.hashTable[index].getData().compareTo(key) == 0){//不能重复插入元素
return;
}
idx = (idx+1)%this.hashTable.length;
}
} while(idx != index);
}
2、删除操作:
当当前元素的哈希值和compareTo方法比较后皆符合使,设当前位元素为空,状态设为used。当遇到空桶时候说明此哈希链中没有此元素,直接返回。
代码实现;
public void remove(T key){
// 先计算key应该放的桶的下标
int index = key.hashCode() % this.hashTable.length;
// 从当前位置开始找元素
int idx = index;
do{
// 如果遍历桶的过程中,发现了从未使用过的桶,直接返回
if(this.hashTable[idx] == null){
return;
}
if(this.hashTable[idx].getState() == State.USING
&& this.hashTable[idx].getData().compareTo(key) == 0){
this.hashTable[idx].setData(null);
this.hashTable[idx].setState(State.USED);
this.usedBucketNum--;
return;
}
idx = (idx+1)%this.hashTable.length;
} while(idx != index);
}