文章目录

  • 1. 字符串相似度
    • 1.1 莱文斯坦距离
    • 1.2 最长公共子串长度
  • 2. 计算编辑距离
    • 2.1 莱文斯坦距离
    • 2.2 最长公共子串长度
  • 3. 搜索引擎拼写纠错
  • 4. 练习题

在 Trie树那节讲过,利用Trie可以进行关键词提示,节省输入时间。
在搜索框中你不小心打错了字,它也会智能提醒你是不是要搜XXX

1. 字符串相似度

  • 如何量化两个字符串的相似度?有一个非常著名方法,编辑距离(Edit Distance)
  • 编辑距离,将一个字符串转化成另一个字符串,需要的最少编辑操作次数(增加一个字符、删除一个字符、替换一个字符)。
  • 编辑距离越大,两个字符串相似度越小;编辑距离越小,两个字符串相似度越大。两个完全相同的字符串,编辑距离是0。

1.1 莱文斯坦距离

  • 莱文斯坦距离(Levenshtein distance)允许增加删除替换字符这三个编辑操作
  • 莱文斯坦距离的大小,表示两个字符串差异的大小

1.2 最长公共子串长度

  • 最长公共子串长度(Longest common substring length)只允许增加删除字符这两个编辑操作
  • 表示两个字符串相似程度的大小

两个字符串 mitcmu 和 mtacnu 的莱文斯坦距离是3,最长公共子串长度是4。

2. 计算编辑距离

这个问题是求把一个字符串变成另一个字符串,需要的最少编辑次数。整个求解过程,涉及多个决策阶段,需要依次考察一个字符串中的每个字符,跟另一个字符串中的字符是否匹配,所以,问题符合多阶段决策最优解模型。

2.1 莱文斯坦距离

回溯解法:
回溯是一个递归处理的过程。如果a[i]与b[j]匹配,我们递归考察a[i+1]和b[j+1]。如果a[i]与b[j]不匹配,那我们有多种处理方式可选:

  • 可以删除a[i],然后递归考察a[i+1]和b[j];
  • 可以删除b[j],然后递归考察a[i]和b[j+1];
  • 可以在a[i]前面添加一个跟b[j]相同的字符,然后递归考察a[i]和b[j+1];
  • 可以在b[j]前面添加一个跟a[i]相同的字符,然后递归考察a[i+1]和b[j];
  • 可以将a[i]替换成b[j],或者将b[j]替换成a[i],然后递归考察a[i+1]和b[j+1]。
/*** @description: 莱文斯坦距离,回溯* @author: michael ming* @date: 2019/7/25 1:25* @modified by: */
#include <string>
#include <iostream>
using namespace std;
void lwstBT(string &a, string &b, int i, int j, int dist, int &minDist)
{if(i == a.size() || j == b.size()){if(i < a.size())dist += (a.size()-i);if(j < b.size())dist += (b.size()-j);if(dist < minDist)minDist = dist;return;}if(a[i] == b[j])// 两个字符匹配{lwstBT(a,b,i+1,j+1,dist,minDist);}else// 两个字符不匹配{lwstBT(a,b,i+1,j,dist+1,minDist);// 删除 a[i] 或者 b[j] 前添加一个字符lwstBT(a,b,i,j+1,dist+1,minDist);// 删除 b[j] 或者 a[i] 前添加一个字符lwstBT(a,b,i+1,j+1,dist+1,minDist);// 将 a[i] 和 b[j] 替换为相同字符}
}
int main()
{int minDist = INT_MAX;string a = "mitcmu", b = "mtacnu";lwstBT(a,b,0,0,0,minDist);cout << "莱文斯坦距离:" << minDist << endl;
}


  • 在递归树中,每个节点状态包含三个变量(i,j,dist),dist表示处理到a[i]和b[j]时,已经执行的编辑次数。

  • 在递归树中,(i,j)两个变量重复的节点很多,比如(3,2)和(2,3)。对于(i,j)相同的节点,我们只保留 dist最小的,继续递归处理,剩下的舍弃。所以,状态就从(i,j,dist)变成了(i,j,min_dist),其中min_dist 表示处理到a[i]和b[j],已经执行的最少编辑次数。

  • 这个问题的状态转移方式,要比之前两节课中讲到的例子要复杂很多。上一节我们讲的矩阵最短路径问题中,到达状态(i,j)只能通过(i-1,j)或(i,j-1)两个状态转移过来,而今天这个问题,状态(i,j)可能从(i-1,j),(i,j-1),(i-1,j-1)三个状态中的任意一个转移过来。

    写状态转移方程

    如果:a[i] != b[j],那么:min_dist(i, j) 就等于:
    min{min_dist(i-1,j)+1,  min_dist(i,j-1)+1,  min_dist(i-1,j-1)+1}如果:a[i] == b[j],那么:min_dist(i, j) 就等于:
    min{min_dist(i-1,j)+1,  min_dist(i,j-1)+1, min_dist(i-1,j-1)}其中,min 表示求三数中的最小值。
    

    根据状态转移方程,填充状态表

/*** @description: 莱文斯坦距离,动态规划* @author: michael ming* @date: 2019/7/25 20:40* @modified by: */
#include <string>
#include <iostream>
using namespace std;
int min(int x, int y, int z)
{int m = INT_MAX;if(x < m)   m = x;if(y < m)   m = y;if(z < m)   m = z;return m;
}
int lwstDP(string &a, const int lenA, string &b, const int lenB)
{int i, j;int minDist[lenA][lenB];for(j = 0; j < lenB; ++j)//初始化第 0 行:a[0..0] 与 b[0..j] 的编辑距离{if(a[0] == b[j])minDist[0][j] = j;else if(j != 0)minDist[0][j] = minDist[0][j-1]+1;elseminDist[0][j] = 1;}for(i = 0; i < lenA; ++i)//初始化第 0 列:a[0..i] 与 b[0..0] 的编辑距离{if(a[i] == b[0])minDist[i][0] = i;else if(i != 0)minDist[i][0] = minDist[i-1][0]+1;elseminDist[i][0] = 1;}for(i = 1; i < lenA; ++i)//按行填状态表for(j = 1; j < lenB; ++j){if(a[i] == b[j])minDist[i][j] = min(minDist[i-1][j]+1, minDist[i][j-1]+1, minDist[i-1][j-1]);elseminDist[i][j] = min(minDist[i-1][j]+1, minDist[i][j-1]+1, minDist[i-1][j-1]+1);}return minDist[lenA-1][lenB-1];
}
int main()
{string a = "mitcmu", b = "mtacnu";cout << "莱文斯坦距离:" << lwstDP(a,a.size(),b,b.size()) << endl;
}

2.2 最长公共子串长度

最长公共子串作为编辑距离的一种,只允许增加删除字符两种操作。表征的也是两个字符串之间的相似程度。

  • 每个状态包括三个变量(i,j,max_lcs),max_lcs表示a[0…i]和b[0…j]的最长公共子串长度。那(i,j)这个状态都是由哪些状态转移过来的呢?
  • 先来看回溯的处理思路。我们从a[0]和b[0]开始,依次考察两个字符串中的字符是否匹配。
  • 如果a[i]与b[j]匹配,最大公共子串长度+1,继续考察a[i+1]和b[j+1]。
  • 如果a[i]与b[j]不匹配,最长公共子串长度不变,这个时候,有两个不同的决策路线:
    1 删除a[i],或者在b[j]前面加上一个字符a[i],然后继续考察a[i+1]和b[j];
    2 删除b[j],或者在a[i]前面加上一个字符b[j],然后继续考察a[i]和b[j+1]。

a[0…i]和b[0…j]的最长公共长度max_lcs(i,j),只有可能通过下面三个状态转移过来:
(i-1,j-1,max_lcs),max_Ics表示 a[0…i-1] 和 b[0…j-1] 的最长公共子串长度;
(i-1,j,max_lcs),max_lcs表示 a[0…i-1] 和 b[0…j] 的最长公共子串长度;
(i,j-1,max_lcs),max_Ics表示 a[0…i] 和 b[0…j-1] 的最长公共子串长度。

状态方程
如果:a[i] == b[j],那么:max_lcs(i, j) 就等于:
max{max_lcs(i-1,j-1)+1, max_lcs(i-1, j), max_lcs(i, j-1)};如果:a[i] != b[j],那么:max_lcs(i, j) 就等于:
max{max_lcs(i-1,j-1), max_lcs(i-1, j), max_lcs(i, j-1)};其中 max 表示求三数中的最大值。
/*** @description: 最长公共子串长度, DP* @author: michael ming* @date: 2019/7/25 21:40* @modified by: */
#include <string>
#include <iostream>
using namespace std;
int max(int x, int y, int z)
{int m = INT_MIN;if(x > m)   m = x;if(y > m)   m = y;if(z > m)   m = z;return m;
}
int lcsDP(string &a, const int lenA, string &b, const int lenB)
{int i, j;int maxlcs[lenA][lenB];for(j = 0; j < lenB; ++j)//初始化第 0 行:a[0..0] 与 b[0..j] 的maxlcs{if(a[0] == b[j])maxlcs[0][j] = 1;else if(j != 0)maxlcs[0][j] = maxlcs[0][j-1];elsemaxlcs[0][j] = 0;}for(i = 0; i < lenA; ++i)//初始化第 0 列:a[0..i] 与 b[0..0] 的maxlcs{if(a[i] == b[0])maxlcs[i][0] = 1;else if(i != 0)maxlcs[i][0] = maxlcs[i-1][0];elsemaxlcs[i][0] = 0;}for(i = 1; i < lenA; ++i)//按行填状态表for(j = 1; j < lenB; ++j){if(a[i] == b[j])maxlcs[i][j] = max(maxlcs[i-1][j], maxlcs[i][j-1], maxlcs[i-1][j-1]+1);elsemaxlcs[i][j] = max(maxlcs[i-1][j], maxlcs[i][j-1], maxlcs[i-1][j-1]);}return maxlcs[lenA-1][lenB-1];
}
int main()
{string a = "mitcmu", b = "mtacnu";cout << "最大公共子串长度:" << lcsDP(a,a.size(),b,b.size()) << endl;
}

最大公共子串长度:4

3. 搜索引擎拼写纠错

  • 当用户在搜索框内,输入一个拼写错误的单词时,拿这个单词跟词库中的单词一—进行比较,计算编辑距离,将编辑距离最小,作为纠正之后的单词,提示用户。

  • 这就是拼写纠错最基本的原理。真正商用的搜索引擎,拼写纠错功能不会这么简单。一方面,单纯利用编辑距离来纠错,效果并不一定好;另一方面,词库中的数据量可能很大,对纠错的性能要求很高。

  • 针对纠错效果不好,有很多种优化思路。
    a. 取出编辑距离最小的TOP10,然后根据其他参数决策选择。比如使用搜索热门程度来决定。
    b. 可以用多种编辑距离计算方法,比如今天讲到的两种,分别计算编辑距离最小的TOP10,求交集,用交集的结果,再继续优化处理。
    c. 可以统计用户的搜索日志,得到最常拼错的单词列表,以及对应的拼写正确的单词。纠错时,首先在这个最常拼错单词列表中查找。一旦找到,直接返回对应的正确的单词。这样纠错的效果非常好。
    d. 引入个性化因素。针对每个用户,维护这个用户特有的搜索喜好,也就是常用的搜索关键词。当用户输入错误时,先在这个用户常用的搜索关键词中,计算编辑距离,查找编辑距离最小的单词。

  • 针对纠错性能方面,讲两种分治的优化思路。
    a. 如果纠错功能的TPS(每秒事务处理量(TransactionPerSecond))不高,可以部署多台机器,每台机器运行一个独立的纠错功能。当有一个纠错请求的时候,通过负载均衡,分配到其中一台机器,来计算编辑距离,得到纠错单词。
    b. 如果纠错系统的响应时间太长,也就是,每个纠错请求处理时间过长,可以将纠错的词库,分割到很多台机器。当有一个纠错请求的时候,将这个拼写错误的单词,同时发送到多台机器,并行处理,分别得到编辑距离最小的单词,然后再比对合并,最终决定出一个最优的纠错单词。

4. 练习题

LeetCode 72. 编辑距离(DP)

LeetCode 1143. 最长公共子序列(动态规划)

动态规划应用--搜索引擎拼写纠错相关推荐

  1. 中文拼写纠错_58搜索拼写纠错

    在搜索引擎中,用户希望得到和输入查询词相关的并且质量较好的网页或文档.但是往往出于各种原因,用户输入的查询词本身质量不高或是错误的,如果搜索引擎不对这种错误进行修正弥补,会导致召回错误的结果,或者结果 ...

  2. SpellBERT:预训练模型与中文拼写纠错

    作者 | 王嘉宁 整理 | NewBeeNLP 大家好,这里是NewBeeNLP.中文拼写纠错在搜索引擎.问答系统中作为入口模块,对其有着至关重要的作用.拼写纠错,即,给定一个自然语言的句子,识别出其 ...

  3. elasticSearch学习笔记04-同义词,停用词,拼音,高亮,拼写纠错

    由于elasticSearch版本更新频繁,此笔记适用ES版本为 7.10.2 此笔记摘录自<Elasticsearch搜索引擎构建入门与实战>第一版 文中涉及代码适用于kibana开发工 ...

  4. java 拼写纠错_拼写纠错的利器,BK树算法

    BK树或者称为Burkhard-Keller树,是一种基于树的数据结构,被设计于快速查找近似字符串匹配,比方说拼写纠错,或模糊查找,当搜索"aeek"时能返回"seek& ...

  5. elasticsearch拼写纠错之Term Suggester

    一.什么是拼写纠错 拼写纠错就是搜索引擎可以智能的感知用户输入关键字的错误,并使用纠正过的关键字进行搜索展示给用户:拼写纠错是一种改善用户体验的功能: elasticsearch提供了以下不同类型的s ...

  6. NLP基础:编辑距离+拼写纠错实战

    NLP基础:编辑距离+拼写纠错实战 1. 编辑距离相关 1.1 编辑距离的计算 1.2 运行结果 1.3 生成特定编辑距离的字符串 1.3.1 生成与目标字符编辑距离为1的字符 1.3.2 运行结果 ...

  7. 基于 BK 树的中文拼写纠错候选召回

    最近在研究中文拼写纠错,在查阅资料的时候看到了这篇文章<从编辑距离.BK树到文本纠错 - JadePeng - 博客园>,觉得 BK 树挺有意思的,决定深入研究一下,并在其基础上重新整理一 ...

  8. 拼写纠错(Spelling Correct)技术方案总结

    目前在做日语纠错任务,主要是为了解决公司query召回率低的问题,目前可行的方案有下面几个: 一个是科大讯飞的那个gector模型 ,他主要是利用了bert或者Robert来做特征提取,然后会在最后接 ...

  9. 中文拼写纠错_中英文拼写纠错开源框架梳理

    一.中文: 1.Pycorrector:https://github.com/shibing624/pycorrector 当前主流的中文纠错框架,支持规则和端到端模型 2.FASPell:https ...

最新文章

  1. Python入门100题 | 第005题
  2. 【完整代码】使用Semaphore实现线程的交替执行打印 A1B2C3D4E5
  3. CoreJava 笔记总结-第九章 集合
  4. 用groovy采集网页数据
  5. 新建网站了!Github标星过万的吴恩达机器学习、深度学习课程笔记,《统计学习方法》代码实现,可以在线阅读了!...
  6. Java实验9 T1.往文件中写入1万个随机数,比较用时的多少
  7. python爬虫(四)_urllib2库的基本使用
  8. 语音识别及其定点DSP实现
  9. css文字竖直显示_CSS属性设置 -- 盒子模型
  10. 游戏开发筑基之特殊输入函数的妙用(C语言)
  11. Python 清屏命令
  12. 【技能】excel小技巧:利用宏制作工资条
  13. 一款神仙儿 MySQL 审核平台,吊到不行!
  14. Android WebView 下载没反应
  15. 一张帖搞定同学们入学黑马前所有难题
  16. 对人工智能芯片的一些看法
  17. 搜狗输入法在idea打不了汉字_IDEA下搜狗输入法输入中文时卡着不动的参考解决方法...
  18. 华为上机英文数字翻译
  19. CS1503号错误是什么
  20. 1218:取石子游戏

热门文章

  1. html间数据传送,Express框架与html之间如何进行数据传递(示例代码)
  2. c语言建立一个链表,每个结点包括姓名和成绩,求C语言几道题的答案~~拜托了~~...
  3. c语言枚举法礼泡声次数,C语言枚举类型举例
  4. long 转为string_面试必问 Redis数据结构底层原理String、List篇
  5. c/c++ 友元基本概念
  6. Android中的IPC机制
  7. 非常经典的C字符串函数的实现
  8. js 获取url的get传值函数
  9. 学习Duwamish7的MSDN说明及相关技术策略
  10. java并发编程实战阅读总结(b)