数据结构与算法之美
王争
前 Google 工程师
283752 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 81 讲
基础篇 (38讲)
数据结构与算法之美
15
15
1.0x
00:00/00:00
登录|注册

42 | 动态规划实战:如何实现搜索引擎中的拼写纠错功能?

分割词库并行处理
多台机器部署
性能优化
引入个性化因素
基于用户搜索日志的纠错
多种编辑距离计算方法
TOP 10选择
优化思路
编程计算最长公共子串长度
编程计算莱文斯坦距离
量化两个字符串的相似度
最长公共子串长度
莱文斯坦距离
最长递增子序列问题
拼写纠错功能
编辑距离
动态规划

该思维导图由 AI 生成,仅供参考

Trie 树那节我们讲过,利用 Trie 树,可以实现搜索引擎的关键词提示功能,这样可以节省用户输入搜索关键词的时间。实际上,搜索引擎在用户体验方面的优化还有很多,比如你可能经常会用的拼写纠错功能。
当你在搜索框中,一不小心输错单词时,搜索引擎会非常智能地检测出你的拼写错误,并且用对应的正确单词来进行搜索。作为一名软件开发工程师,你是否想过,这个功能是怎么实现的呢?

如何量化两个字符串的相似度?

计算机只认识数字,所以要解答开篇的问题,我们就要先来看,如何量化两个字符串之间的相似程度呢?有一个非常著名的量化方法,那就是编辑距离(Edit Distance)。
顾名思义,编辑距离指的就是,将一个字符串转化成另一个字符串,需要的最少编辑操作次数(比如增加一个字符、删除一个字符、替换一个字符)。编辑距离越大,说明两个字符串的相似程度越小;相反,编辑距离就越小,说明两个字符串的相似程度越大。对于两个完全相同的字符串来说,编辑距离就是 0。
根据所包含的编辑操作种类的不同,编辑距离有多种不同的计算方式,比较著名的有莱文斯坦距离(Levenshtein distance)和最长公共子串长度(Longest common substring length)。其中,莱文斯坦距离允许增加、删除、替换字符这三个编辑操作,最长公共子串长度只允许增加、删除字符这两个编辑操作。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了搜索引擎中拼写纠错功能的技术原理和实现方法。通过介绍编辑距离的概念和计算方法,包括莱文斯坦距离和最长公共子串长度,文章深入探讨了如何快速计算两个字符串之间的编辑距离。通过具体例子和代码实现,读者可以快速了解搜索引擎拼写纠错功能的背后技术原理,以及如何利用编辑距离来实现字符串相似度的量化和计算。此外,文章还分享了解决复杂算法问题的思考方式和经验,为读者提供了解决类似问题的思路和方法。在文章结尾,作者提出了拼写纠错功能的优化思路,包括多种编辑距离计算方法、统计用户搜索日志等。同时,针对纠错性能方面,提出了分治的优化方式。整体而言,本文内容深入浅出,适合对搜索引擎技术感兴趣的读者阅读学习。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《数据结构与算法之美》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(179)

  • 最新
  • 精选
  • zixuan
    补充一下,中文纠错很多时候是通过拼音进行的,比如 "刘得花"->"liudehua"->"刘德华". 拼音检索方法也有很多,比如可以把热门词汇的拼音字母组织成Trie树,每个热词的结尾汉字的最后一个拼音字母就是叶子,整体性能就是O(n)的,n为query的拼音总长度. 除了拼音外也有根据字形(二维文字版的编辑距离?)甚至语义等做的纠错策略。 传统搜索引擎中的查询词智能提示、纠错、同义词、近义词、同好词、相关搜索、知识图谱等系列功能统称为用户的意图识别模块。

    作者回复: 👍好厉害

    2019-01-03
    4
    198
  • blacknhole
    有个疑问: 以下内容涉及“如何编程计算莱文斯坦距离?”一节。 (1)文中对递归树中的状态三元组(i, j, edist)的解释是,“状态包含三个变量 (i, j, edist),其中,edist表示处理到 a[i] 和 b[j] 时,已经执行的编辑操作的次数。”这里的“处理到a[i]和b[j]时”,其实是在说将要处理但还并未处理a[i]和b[j]。edist并不包括对a[i]和[j]的编辑操作。递归树图片后紧接着的图片中,(i, j, min_edist)的min_edist也并不包括对a[i]和[j]的编辑操作。 (2)而二维状态表图片中每格的值和动态规划的实现代码中minDist[i][j]两者均代表:到处理完a[i]和b[j]之后为止,已经执行的编辑操作的最少次数。根据这个意思,可知状态转移方程中的min_edist(i, j)也是包括对a[i]和[j]的编辑操作的。如果按照(1)中的意思,状态转移方程中的min_edist(i, j)就不应该包括对a[i]和[j]的编辑操作,也不应该判断a[i]和b[j]是否相等,而应该判断的是a[i - 1]和b[j - 1]是否相等;并且动态规划的实现代码中循环终止条件就不应是小于n或m,而应是小于等于n或m。 为什么会有(1)与(2)这样的在文章前后表达上的不一致?

    作者回复: 👍你说的没错 不仅这一节 前面两节课都是这样。递归树是根据回溯算法代码实现写的。但是动规代码用你讲到的另一种思路理解更容易。我当时写的时候 也想能不能统一。最后发现不好统一。你可以分割开来看 或者 把它们当作两种状态表示方法来看 不影响理解

    2019-01-02
    18
    56
  • 天使之剑
    是不是可以这么理解,如果要列出所有可能的情况的通常用回溯算法,而求最佳的情况回溯和dp都可以用,但是有重复子问题的话,可以用dp降低时间复杂度

    作者回复: 是的 你理解的没错

    2019-01-02
    31
  • saber
    老师我有个疑问(急),希望老师能够帮忙回答下,这也是大部分留言中没能解决的疑问。 在莱文斯坦距离讲解时,那里的状态方程是怎么直接写出来的?这一点是我最不能理解的,当然在问该问题之前,我已经尽我所能的尝试理解老师的文章,并且把留言也都翻了一遍。老师请看我下面的陈述: 一:按照老师文章的讲解顺序,先分析了如何用回溯方法,然后绘制了递归树,从递归树中又分析出了具有重复的子结构,接着又画出了状态简图(递归树下面的图,不知道怎么表达),从这幅简图中清晰的看出来状态的转移方式,接下来重点来了!!!文章接着就写出来状态转移方程(不理解的地方)!我在留言中发现,老师对这点的解释是说「状态转移方程与递归树无关,递归树仅仅表达了问题具有重复子结构」,如果按照老师的说法,不根据递归树分析出状态转移过程,那该怎么直接写出状态转移方程(要能够合理的推断出来,而不是靠经验,否则还是无法掌握动态规划)?目前来看,我在文章中没有发现具有这样的逻辑推断。我之前的理解是说,我们其实可以在递归树中分析出状态转移方程如何写,然后这样就形成了一个闭环,由回溯->递归树->状态转移方程。希望在这里老师能够给出一个合理的解释? 二:抛开上面的一些问题,咱们单独看莱问斯坦距离状态转移公式,先假设,按照递归树和递归树下面的图可以知道状态转移的过程(这是一个前提假设)。在留言中的第 4 条「blacknhole」这位同学的留言中,我也有相同的疑问,(i , j, min_edist) 到底表达的是什么状态?(处理完 a[i] b[j] 后的最小距离? or 刚要处理 a[i] b[j] 时对应的最小距离?), 接下来按照二种不同的表达来看看状态方程如何推断出来: 1、 如果表达的含义是 「处理完 a[i] b[j] 后的最小距离」 那么其实从回溯算法中可以知道(因为 i j 的前一个状态已经处理完了,比如 min_edist(i-1,j) 就代表了前一个状态。) if (a[i] != b[j]) ​ min_edist(i, j) =1+ min( min_edist(i-1, j), min_edist(i, j-1), min_edist(i-1,j-1)) if (a[i] == b[j] ) ​ min_edist(i, j) = min( min_edist(i-1, j), min_edist(i, j-1), min_edist(i-1,j-1)) 2、如果表达的含义是「刚要处理 a[i] b[j] 时对应的最小距离」 那么我们可以这么想,当前 i,j min_edist 表示还没有处理过 i,j 时对应的状态,因此我们需要考虑之前的状态是怎么传递到这里来的 min_edist(i, j) = min(min_edist(i-1,j)+1, min_edist(i, j-1)+1, min_edist(i-1, j-1) + (a[i-1] == b[j-1]? 0 : 1)) 从上面的两种推断发现,我觉的都是比较合理的,但是没有一种的结果与老师给的状态转移方程是一致的(这一点比较疑惑)?所以对于老师的状态转移方程肯定不是从递归树中直接推导出来的,那么请问老师这个状态转移方程该怎么合理的推断出来(我觉得这个问题不是靠多做题解决的,必须要知道推理逻辑才能彻底掌握动态规划)? 三:接下来还有一个问题(前提假设是老师的状态方程是正确的,具体逻辑还不能理解) 那么如果根据状态状态方程,来填充状态表的第 0 行 第 0列呢(留言中也有许多这方面的疑问)?按照我的理解,既然状态转移方程我们知道了,那么其实从状态转移方程中就可以直接填充表格了,比如填充第 0 行时,因为从状态转移方程中我们发现有 i - 1,j-1 因此当计算第 0 行时,带有 i-1 项的就可以省略了,因此状态转移方程就剩下 (i, j-1) 项了。然后其实就不用分 a[i] b[j] 是否相等了,但是回头看老师的代码,却不明白为什么还要分 a[i] b[j] 相等不相等。希望老师能够解释一下! 综上:如果老师觉得我描述的问题不清楚,那么希望老师看看这篇文章的其他问题留言,总结一下大家到底哪里不理解,并且对不理解的地方从新更新下当前文章,具体解释一下,比如老师在留言中说过「不能从递归树中直接推导出状态转移方程,两者没有联系」,那么希望老师在文章的对应地方直接表明这句话,并且在加上额外的说明,比如如何推断出状态转移方程,这样整篇文章才能让更多的人从本质上理解动态规划! 最后希望老师能够认真给出我上面几个问题的解答,谢谢老师!

    作者回复: 你这个问题太多了 我写篇文章给你 你关注:小争哥

    2019-09-24
    15
    11
  • Ricky
    老师,您好,您这节内容讲的很清晰透彻,我以前做动态规划问题是直接寻找状态转移方程,基本只能处理一些简单的动态规划问题,没有形成系统的解题思路,听了您这一节后,我觉得将回溯简单思路逐步转化为动规思路让我受益匪浅,但是当我试着将这套思路应用于求解最长递增子序列时却感觉回溯更麻烦,不知能否指点一二

    作者回复: 我的讲解并不是为了指导做题。所以,我讲解的时候,从为什么要动态规划讲起,所以废话比较多。实际上,你理解理论之后,解动态规划题目的时候,一般可以直接写最优子结构,也就是状态转移方程,不需要再从回溯解法开始。。。

    2019-01-04
    11
  • 传说中的成大大
    if (a[i] == b[j]) minDist[i][j] = min( minDist[i-1][j]+1,minDist[i][j-1]+1,minDist[i-1][j-1])不明白的是为什么minDist[i-1][j]或者mindist[i][j-1] 要+1呢?

    作者回复: (i-1,j)这个状态转移到(i, j)这个状态,minDist要加一 (i, j)同上 (i-1, j-1)这个状态转移到(i, j)这个状态,minDist保持不变,因为a[i]==b[j]

    2019-02-28
    9
    5
  • slvher
    本文举例的 LCS 问题不要求字符连续,通常是指 longest common subsequence 吧?

    作者回复: 是的

    2019-01-02
    3
  • 梅坊帝卿
    通过思考题再次发现 找到状态转移方程是多么重要 其他基本都是套路 迭代取极值 到结束

    作者回复: 光讲状态转移方程 新手是没法很好的理解的

    2019-01-08
    2
  • Zix
    老师状态转移方程,当a[i] = b[j] 的时候,能否把 minDist[i][j] = min(minDist[i-1][j]+1, minDist[i][j-1]+1, minDist[i-1][j-1]); 改写为 minDist[i][j] = minDist[i-1][j-1]; ?请求老师解答

    作者回复: 当然不可以了,改写之后完全不对呢,你对比一下前后两个公式。

    2019-05-26
    1
  • Geek_41d472
    我怎么感觉,分析完问题后,能不能写出代码的关联点是能不能通过前面的分析写出状态转移方程,跟着老师学的越多,越感觉自己是以前真的是个码农,根本算不上程序员。虽然我才真正做java一年,还是半路出家的,跟着老师学到好多东西,哈哈

    作者回复: 你说的没错。不过单纯讲如何找状态转移方程,如何根据状态转移方程写代码,这是面试流了。。。

    2019-01-08
    1
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部