Word文档编辑器大家应该经常使用吧,大家有没有留意到它编辑功能,当我们输入一个错误的单词时,单词单面就会标红提示“拼写错误”,这个功能是怎么实现的呢?其实啊,它是通过散列表实现的,学习了散列表原理后你就懂得这个功能的实现方式了。

散列表

散列表的英文名叫Hash Table,一般叫散列表或哈希表,散列表用的是数组支持按照下标随机访问数据的特性,所以散列表就是数组的一种扩展,由数组演化而来,可以说,如果没有数组就没有散列表。

我用一个列子解释一下,我们去游泳馆游泳时一般都会寄存衣物,这时前台就会登记我们名字后分配一个储物柜编号卡,后面我们通过这个编号卡就能快速地找到柜子存储衣物,回去时也能快速找到柜子取回衣物。

这里储物柜是按照编号顺序排列,就相当于一个数组,由于每天去游泳的人都各不相同,就不能每个柜子都贴上对应人的名字了,所以储物前就会先去前台分配一个编号,再根据编号的下标存储在数组的下标位置。

这就是典型的散列思想。每个去游泳的人的名字我们叫做(key)或者关键字。我们把前台通过名字分配储物柜号的对应过程叫作散列函数,而通过散列函数计算得到的储物柜号码叫作散列值

散列函数

散列函数,顾名思义,它就是一个函数,我们可以把它定义为hash(key),其中key就是元素的键,hash(key)就是通过散列函数计算得到的散列值。

刚刚举的例子中,散列函数其实就是前台工作人员将名字和号码牌对应起来的一个对应关系,这个例子比较不恰当,并没有一个固定的公式。那么,实用场景中,要怎么设计构造散列函数呢,我总结了三点基本的要求:

  1. 散列函数计算得到的散列值必须是一个非负整数;
  2. key1=key2,那hash(key1)=hash(key2);
  3. key1≠key2,那hash(key1)≠hash(key2);

第一点很容易理解,散列值最后是作为数组的下标的,数组下标是从0开始的;第二点,相同的key,得到的散列值也应该是相同的。

第三点看起来合情合理,但是在真实场景中,要想找到一个不同的键得出的散列值都不一样的散列函数几乎是不可能的,即便像业界著名的MD5、SHA、CRC等哈希算法也无法避免散列冲突,因为数组的空间有限,函数计算得到的值还必须在数组个数范围内,因此就会有很大概率出现冲突。

散列冲突

再好的散列函数也无法避免散列冲突,那怎么办呢?只能通过其他方式解决,一般散列冲突的解决办法有两类:开放寻址法链表法

1.开放寻址法

开放寻址法的核心思想是,如果出现了散列冲突,就寻找下一个空闲位置,插入新的数据。开放寻址法也有多种方式,将介绍一个简单的探测方法,线性探测(Linear Probing)。

线性探测

当我们往散列表插入数据时,如果经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置依次往后查找,将数据插入到找到的空闲位置,如果遍历到尾部仍没有空闲位置,我们就从表头开始找,直到找到为止。如图所示

通过线性探测要查找数据时,和插入数据类似,也是通过散列函数得到对应位置的元素,和要查询的数据作对比,如果一致,则取出该值,如果不一致,则从该位置往下到散列表的空闲位置一个个查找,如果找到,取出对应值,如果没有找到,则数据不存在。

而但通过线性探测法删除一个元素时就比较麻烦,如果查找到对应的元素时直接将该元素对应的位置置空的话,那按照上面说的线性探测查找方法,遇到一个空的位置时就停止查询,那这个空位置如果是刚刚被删除的元素,这时候这个查找方法就失败了。所以,删除一个元素时并不是直接删除,而是在要删除的位置标记deleted。在查找一个元素时如果遇到deleted标记的元素,则继续往下查找,如下图所示。

通过上面的介绍,我们可以知道,线性探测法有一个弊端。就是散列表剩余空间不足时,就会频繁地出现散列冲突,导致效率不高,极端情况下插入一个元素会时间复杂度为O(n)。

对于开放寻址的冲突解决方法,除了线性探测方法外,还有另外两种比较经典的探测方法,分别为二次探测双重散列

二次探测

二次探测法,和线性探测法类似,线性探测每次的探测步长是1步,它探测的下标序列是hash(key)+0,hash(key)+1,hash(key)+2,hash(key)+3...而二次探测的步长是原来的“二次方步长”,它的探测下标序列是列是hash(key)+0,hash(key)+1,hash(key)+4,hash(key)+9...

双重散列

双重散列,意思是不仅要使用一个散列函数,我们要使用一组散列函数hash1(key),hash2(key),hash3(key)...先使用第一个散列函数计算散列位置,如果出现冲突,再使用第二个散列函数,依次类推,直到找到空闲位置。

不管使用哪种线性冲突解决方法,当空闲位置较少的时候,出现冲突的概率就会加大,为了保证散列表的操作效率,一般会保证散列表有一定比例的空闲位置,我们用装载因子来表示散列表的空闲比例,它的计算公式如下

散列表的装载因子 = 填入表中的元素个数 / 散列表长度

2.链表法

链表法是一种更加普遍的散列表冲突解决方法,相比线性探测法,它更简单更容易理解。如图所示,散列表的元素就是一个“桶”或“槽”,每个桶都放入一个链表,将散列值相同的元素都放在同一个链表中。

当插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应的链表中即可,时间复杂度为O(1)。当要查询或删除时,即通过同样的方法找到对应的槽位,再遍历链表查询或删除,那查询和删除的时间复杂度是多少呢?

查询和删除的时间复杂度和每个槽位链表的长度成正比,假设链表平均长度为k,那时间复杂度则为O(k)。对于散列比较均匀的散列函数,理论上k = n / m,其中n为散列表中数据的个数,m为散列表的长度。

解答开篇

有了上面的散列表的介绍,我们再来回顾下开篇提到的Word文档编辑器的拼写错误提示是怎么实现的?

我们常用的英文单词大约有20万个左右,假设平均每个单词有10个字母,那每个单词就大约有10个字节,20万个单词就有差不多2M左右的大小,对于现代的计算机来说,完全可以将20万个单词放在内存中,存储在散列表,每次输入一个单词时,就通过散列表查找,如果能找到就是拼写正确的,如果找不到则提示拼写错误。

pymongo查询列表元素_散列表:如何实现word编辑器的拼写检查?相关推荐

  1. 散列表的设计与实现_散列表:如何实现word编辑器的拼写检查?

    Word文档编辑器大家应该经常使用吧,大家有没有留意到它编辑功能,当我们输入一个错误的单词时,单词单面就会标红提示"拼写 错误",这个功能是怎么实现的呢?其实啊,它是通过散列表实现 ...

  2. 列表根据下标取值_散列表(上):Word文档中的单词拼写检查功能是如何实现的?...

    Word这种文本编辑器你平时应该经常用吧,那你有没有留意过它的拼写检查功能呢?一旦我们在Word里输入一个错误的英文单词,它就会用标红的方式提示"拼写错误".Word的这个单词拼写 ...

  3. JAVA散列表个人通讯录_散列表实现简易通讯录

    散列表实现通讯录 1.项目研究背景与意义 背景:随着信息活动在国民经济中主导地位的确立和信息产业的崛起,信息资源管理作为一个专有名词和独立的学科逐渐发展起来.如何积极开发.合理配置和有效利用信息资源, ...

  4. pymongo查询列表元素_使用PyMongo查询MongoDB数据库!

    作者|LAKSHAY ARORA 编译|Flin 来源|analyticsvidhya 介绍 随着互联网的普及,我们现在正以前所未有的速度生成数据.因为执行任何类型的分析都需要我们从数据库中收集/查询 ...

  5. 利用开放定址法实现散列表的创建、插入、删除、查找操作_散列表和IO

    散列表(也叫哈希表) 直接寻址法 取关键字或关键字的某个线性函数值为散列地址.即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数).若其中H(ke ...

  6. python删除列表元素_追求简单C++之删除STL列表的元素

    本文介绍了STL列表中的删除方面. 1.使用list :: erase():此函数的目的是从列表中删除元素.使用此功能可以删除范围内的单个或多个连续元素.该函数有两个参数,开始迭代器和结束迭代器. 2 ...

  7. python如果选择不在列表里_使用python中的in ,not in来检查元素是不是在列表中的方法...

    使用python中的in ,not in来检查元素是不是在列表中的方法 更新时间:2018年07月06日 15:01:34 作者:mengtianwxs 今天小编就为大家分享一篇使用python中的i ...

  8. C# list删除 另外list里面的元素_[Python]列表(list)操作

    对于python列表的理解可以和C语言里面的数组进行比较性的记忆与对照,因为它们比较相似,对于python里面列表的定义可以直接用方括里加包含对象的方法,并且python的列表是比较强大的了,它包含了 ...

  9. Python学习十四:访问列表元素、遍历列表

    一.访问列表元素 在python中,如果想将内容输出,可以使用print()函数.例如,我们要打印下面的列表: untitle = ['python',28,"我开心,我快乐",[ ...

最新文章

  1. display:inline-block+text-align:justify实现列表元素的两端对齐
  2. 响应式开发一招致胜 学习视频 分享
  3. Bootstrap3.0学习第九轮(CSS补充)
  4. ue4场景没阴影_UE4性能调试分析常用方法
  5. 微软发布Win 10 开始按钮真正回归明年底上市
  6. 使用CSS属性处理前端开发中长文本造成的内容显示重叠问题
  7. 最小生成树KrusKal算法(并查集)
  8. java 使用本机代理_Java与本机代理–他们所做的强大功能
  9. 将matlab中数据输出保存为txt或dat格式
  10. vim学习笔记(3)眼花缭乱的Vim模式
  11. 我们渴望和平freeeim
  12. transformer学习
  13. 思杰修复网络产品中的11个漏洞
  14. jsp中excel文件的创建与读取
  15. 【LeetCode】【数组】题号:*289,生命游戏
  16. 文言文代码算什么?跟着九章算术学Python编程才厉害
  17. 3.7V升压5V,3.7V转5V电路图芯片
  18. python 过采样算法_类不平衡数据分类准确率的提升算法smote过采样方法
  19. NEFU OJ 1266-快乐的雨季-线段树【题解】
  20. c++primer 第二章 变量和基本类型

热门文章

  1. wpf怎么让grid表格中元素显示到最顶层_一文搞定PPT中的快捷键
  2. ZCMU 1048: 子串
  3. 居然之家:核心业务系统全面上云,采用PolarDB替代传统商业数据库
  4. 倒计时 | 7.24 阿里云 Serverless Developer Meetup 杭州站报名火热进行中!
  5. 核桃编程 | 前端可观测性建设之路
  6. Fluid 0.4 新版本正式发布
  7. 盒马鲜生,快而准确的秘密!
  8. go语言项目优化(经验之谈)
  9. JUST技术:空间连接运算与空间索引
  10. 【忘川风华录】可爱的大“装备”?名士猫交互设计复盘