Word文档中的单词拼写检查功能是如何实现的?
散列表,“Hash Table”,平时也叫“哈希表”,采用的是数组支持按照下标随机访问,相当于数组的一种扩展。
例如:学生参加学校运动会,100名选手每个人都有一个参赛编号,那么如何快速通过参赛编号找到运动员呢?那我们就可以将参赛编号与数组下标一一对应起来,当需要查询参赛编号x的时候,只需要将其从对应下标为x,从数组中取出来即可,就可以实现快速查找编号对应的选手信息了。
这里就用到了散列思想,参赛选手的编号作为key或者关键字,将编号映射为数组下标的方法就是散列函数,得到的值就是散列值;
散列函数设计要求:
开放寻址法的核心思想是,如果出现了散列冲突,就重新探测一个空闲位置将其插入;
探测方法
线性探测法
从当前位置依次往后查找,看是否有空闲位置,直到找到为止。
探测算法的弊端:
当散列表中插入的数据越来越多时,散列冲突发生的可能性就越来越大,空闲位置会越来越少,线性探测的时间越久,在极端情况下的时间复杂度是O(n);
二次散列(Quadratic probing)
线性探测的步长是1,那么它探测的下标序列就是hash(key)+0,hash(key)+1,hash(key)+2…,二次探测的步长就变成了原来的"二次方",
hash(key)+0,hash(key)+1^2…
双重散列(Double hashing)
双重散列,就是不仅要使用一个散列函数,要用一组散列函数hash1(key),hash2(key),hash3(key)…我们先用第一个散列函数,如果计算得到的存储位置已经被占用,再用第二个散列函数,依次类推,直到找到空闲的存储位置。
当散列表中的空闲位置不多时,不管哪种探测方法,散列冲突的概率都会大大提高。
在散列表中,每个“桶(bucket)”或者"槽(slot)"都会对应一条链表,所有散列值相同的饿元素都会放到相同槽位对应的链表中。
当插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度是O(1)。当查找、删除一个元素时,我们同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除。那查找或删除操作的时间复杂度是多少呢?
实际上,这两个操作的时间复杂度跟链表的长度k成正比,也就是O(k)。对于散列比较均匀的散列函数来说,理论上讲,k=n/m,其中n表示散列中数据的个
数,m表示散列表中“槽”的个数。
散列函数的设计不能太复杂,需要考虑关键字的长度、特点、分布还有散列表的大小等,散列函数的设计方法有,直接寻址法、平方取中法、折叠法、随机数法等;
装载因子 = 填入表中的元素个数/散列表的长度;
装载因子越大,说明散列表的元素越多,空闲位置越小,散列冲突的概率越大;
当散列因子过大时,我们也可以进行动态扩容
Java中的HashMap
初始大小
HashMap默认的初始值大小为16,可以修改默认初始大小,减少动态扩容次数,大大提高HashMap的性能;
装载因子和动态扩容
最大装载因子为0.75,当HashMap中的元素个数超过0.75*capacity的时候就会启动自动扩容,每次扩容都会变为原来的两倍;
散列冲突解决方法
HashMap的底层采用链表法来解决冲突,当链表长度过长(默认值为8)时,链表就转换为红黑树,可以利用红黑树快速增删改查,提高HashMap的性能
散列函数
简单高效,分布均匀;
借助散列表可以将LRU缓存淘汰算法的时间复杂度降低为O(1);
一个缓存**(cache)**系统主要包含以下几个操作:
在有序集合中,每个成员对象有两个重要的属性,key(键值)和score(分值)。我们不仅会通过score来查找数据,还会通过key来查找数据;
LinkedHashMap也是通过散列表和链表组合在一起实现的。实际上,它不仅支持按照插入顺序遍历数据,还支持按照访问顺序来遍历数据;
以下面这段代码为例:
HashMap<Integer,Integer> m = new LinkedHashMap<>();
m.put(3,11);
m.put(1,12);
m.put(5,23);
m.put(2,22);
for(Map.Entry e:m.entrySet()){
System.out.println(e.getKey());
}
最终输出的结果是3,1,2,5
//初始大小为10,0.75是装载因子,true是指按照访问时间排序
HashMap<Integer,Integer> m = new LinkedHashMap<>()(10,0.75f,true);
m.put(3,11);
m.put(1,12);
m.put(5,23);
m.put(2,22);
m.put(3,26);
m.get(5);
for(Map.Entry e:m.entrySet()){
System.out.println(e.getKey());
}
这段代码的输出为1,2,3,5;
思考一下为啥会这样:
咱们看一下过程,当调用完前4个put函数的时候,链表中的数据如下图所示:
将键值为3的数据再次假如LinkedHashMap的时候,先查找这个值是否存在,将已经存在的(3,11)删除,并将新的(3,26)加入链表尾部,此时的链表数据如下图所示:
当执行m.get(5)的时候,需要将key为5的数据取出,插入链表的尾部,此时链表中的数据变为:
最终打印的数据就是1,2,3,5
LinkedHashMap是通过双向链表和散列表这两种数据结构组合实现的LinkedHashMap中的“Linked”实际上是指的是双向链表,并非指用链表法解决散列冲突。
Word文档中单词拼写检查功能如何实现?
常用的英文单词有20万个左右,假设单词的平均长度是10个字母,平均一个单词占用10个字节的内存空间,那20万英文单词大约占2MB的存储空间,就算放大10倍也就是20MB。对于现在的计算机来说,这个大小完全可以放在内存里面。所以我们可以用散列表来存储整个英文单词词典。
当用户输入某个英文单词时,我们拿用户输入的单词去散列表中查找。如果查到,则说明拼写正确;如果没有查到,则说明拼写可能有误,给予提示。借助散列表这种数据结构,我们就可以轻松实现快速判断是否存在拼写错误。