搜索
1.什么是搜索?
2.搜索面临的问题?
1)尽可能快的查到结果
2)数据集合还需要适应变化(数据集合的数据是可能实时变化着的)
3.搜索中存在的模型
3.设计专门的数据结构来解决搜索(查找)问题
哈希表背后的机制
1.hash表就是利用了数组的访问下标是O(1)的性能,把在大数据集中查找的问题,转化为在多个小数据集中查找。如下图,要在500万个数中查找,数据集明显很大,此时我们可以转换为小数据集,然后根据下面的步骤进行查找
2、所以接下来要做的就是:
1)拿着key确定应该是哪个小集合的问题:O(1)
首先利用key求出Key对应的hash值,然后利用hash值得到数组中的合法下标
2)根据下标,找到小集合(利用数组下标访问O(1)特性
为了提升空间利用率,数据的个数N是远远大于L的,所以必然会后一个下标处出现多个Key
不同的key对应相同下标的情况叫做哈希冲突
哈希冲突![在这里插入图片描述](https://img-blog.csdnimg.cn/20200622174305466.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0OTI5NjUy,size_16,color_FFFFFF,t_70)
所以要想降低冲突发生的的概率,就要降低负载因子LF(N/L),但数据N的数量是不能改变的,所以只有通过提升N来降低冲突率,也就是对数组进行扩容,即扩容的目的就是为了降低冲突率。
3、遇到哈希冲突的解决办法
之前的文章也提到过:哈希表初识 这里再简单描述一下
1)在本数组内解决(开放定址法),事先制定一个原则,如果冲突了,就按照这个规则找下一个下标即可,直到找到可有位置。
2)在数组下标位置处挂一个链表,把所有冲突数据全部放到这个链表中(拉链法),这个方法比较常用,基于key的个数不是很多
HashMap的插入过程实现
如果一个链表中,插入的结点过多了(在控制了1负载因子的情况下,仍然发生这样的情况,说明1)key的分布严重不均匀了 2)key的hashCode设计的不好
3、哈希表本质上是把大数据集的查找问题,转化为n多个小数据集的查找问题
1)当小数据集长度不大时,链表足以
2)当小数据集长度太大时,如果解决一个数据集中查找的问题:
哈希表 搜索平衡树(红黑树)
转化为红黑树的阈值时是8,也就是说当某个链表上的数值超过8个时,就转化为红黑树
4、一个自定义的key作为hashMap的key,需要重写hashCode和equals方法,因为过程中需要用到这两个方法
5、哈希的用处:1)哈希表 2)验证源数据是否是正确的
HashMap的实现
查找过程
public V get(K key) {
//时间复杂度是O(1)
//1.根据key得到下标
//1.1求出key的hashcode()
/**
* 这里使用了hashCode()
* 如果要使用自定义的类作为hashMap的k类型
* 就必须要在类中覆写hashCode()且相同的key的hashCode()相等
* 否则去发根据相同的key找到唯一的下标
*/
int hash=key.hashCode();
//1.2利用hash得到合法的下标
/**
* 这个的作用是让hash中的每一位都参与到找下标的过程
* 使得找到的下标尽可能均匀
*/
hash=(hash>>>16)^hash;
/**
* 前提:这里利用了table.length一定是2的幂次方
* 利用位运算使得下标大于0并且小于table.length
*/
int index=hash&(table.length-1);
//2.使用下标找到链表的头结点的引用
Entry<K,V> head=table[index];
//3.在链表中找到包含key的节点,返回节点中的value
//遍历链表 去查找
Entry<K,V> node=head;
while(node!=null){
/**
* 如果使用自定义类作为hashMap的key
* 必须覆写equals()方法,保证你认为相同的Key,equals返回值也相同
* 否则无法正确找到Value
*/
if(key.equals(node.key)) {
return node.value;
}
node=node.next;
}
//遍历结束,没有找到
return null;
}
插入过程
public V put(K key, V value) {
int hash=key.hashCode();
hash=(hash>>>16)^hash;
int index=hash&(table.length-1);
Entry<K,V> head=table[index];
Entry<K,V> node=head;
while(node!=null){
if(key.equals(node.key)){
//使用新的value,替换原来的value
//并且返回原来的value
V oldValue=node.value;
node.value=value;
return oldValue;
}
node=node.next;
}
//没有找到使用key-value新建节点,并且把节点插入到链表中
Entry<K,V> entry=new Entry<>(key,value);
//头插OR尾插 这里使用尾插
Entry<K,V> la=head;
if(la==null){
table[index]=entry;
}else{
Entry<K,V> la=head;
while(la.next!=null){
la=la.next;
}
la.next=entry;
}
size++;
//为了减少冲突率,所以需要考虑降低size/table.length
//设定一个阈值,当size/table.length高于某个值时,进行扩容
//所谓的扩容,就是保证size不变,让table。length变大
//进而使得size/table.length降低,进而降低冲突率
if(size*1.0/table.length>LOAD_FACTOR){
resize();
}
return null;
}
扩容过程
/**
* 所谓的扩容,必须保证数组长度是2的幂次方
* 因为table.length变化了,所以key对象的下标一定会变化
* 所以,需要把所有的key重新计算下标重新插入
*/
private void resize() {
Entry<K,V>[] newTable=new Entry[table.length*2];
for(int i=0;i<table.length;i++){
Entry<K,V> head=table[i];
Entry<K,V> node=head;
while(node!=null){
K key=node.key;
V value=node.value;
int hash=key.hashCode();
hash=(hash>>>16)^hash;
int index=hash&(newTable.length-1);
Entry<K,V> entry=new Entry<>(key,value);
entry.next=newTable[index];
newTable[index]=entry;
node=node.next;
}
}
table=newTable;
}