总结一句话:编辑距离就是从一个字符串变到另一个字符串所须要最小的步骤html
在信息论、语言学和计算机科学中,Levenshtein distance是用于测量两个字符串之间差别的字符串度量。非正式的说就是两个单词之间的Levenshtein distance是将一个单词更改成另外一个单词所需的单字符编辑(插入,删除或替换)的最小步骤。java
它以苏联数学家弗拉基米尔·莱文斯坦(Vladimir Levenshtein)的名字命名,做者在1965年提出的这个算法。c++
Levenshtein distance也能够称为编辑距离,尽管该术语也能够表示更大的距离度量系列。算法
Levenshtein distance与成对字符串对齐密切相关。数组
这里面主要内容为我对Levenshtein distance的英文翻译,也加了一些个人想法~网络
在两个字符串a和b之间的Levenshtein distance由下面 定义: post
其中 测试
其中相对于a变化到b字符串来讲:spa
咱们计算一下kitten和sitting之间的编辑距离翻译
上面的变化过程所须要的步数就是最小的步数,因此他们之间的编辑距离就是"3"
Levenshtein distance数值包含几个上下界限
Hamming distance 是两个相同长度的字符串从头开始分别比对两个字符串对应字符位置的值是否相同,不相同则距离加1,最后获得的结果就是 Hamming distance 例如abcd、abhg的距离为2,abcd、bcda的距离是4
笔者在作一个关联网络项目时,后台有两种特别数据:地址和公司,这两种数据都是用 户本身输入的数据,因此特色就是同一个地点可能有多种不一样的字符串,就好比同一个地点:“北京市朝阳区IT产业园“,在后台数据中可能有“北京朝阳区IT产业园”或者“北京朝阳区it园”等一系列数据,咱们又不能去作模糊查询(由于节点数据和边关系为千万级的,模糊查询可能会匹配到大量的节点返回致使返回大量的数据影响项目稳定),咱们就采用了数据对齐的方式解决这个问题,当用户输入一个地址时,咱们经过编辑距离算法就能够获取到其余相关的数据显示出来,就能够达到一个比较好的效果。具体的实现步骤就不在此介绍了。
笔者所在公司就有一个公司内部提供的拼写纠错的组件,其中就有一部分使用了编辑距离算法。下面是组件的简单介绍:
纠错主要解决 query 中输入错误的状况,好比 query 为拼音,query中包含同音错别字或不一样音的别字,漏字的状况等等。 本纠错主要基于两种规则:拼音纠错和编辑距离纠错。 离线主要生成两个词典,即拼音词典和编辑距离词典。来源词典主要来自于 cmc 数据,小区数据,topquery,以及白名单数据等。经过 ****脚本 生成拼音词典和编辑距 离词典。脚本执行完以后,会在 ***目录 下生成词典数据。拼音词典的生成主要是未来源词典中的词转换为拼音,编辑距离词典的生成主要是省略某个字或者某个拼音的字母生成的。生成字典的代码在 tool 下。 在线纠错逻辑 经过 make 编译代码能够生成 so 目录下的动态连接库。 对外提供的是 java RPC 服务,经过 java jni 连接 c++动态连接库。
主要纠错逻辑以下:首先对 query 解析,判断是全拼音或包含中文。若全是拼音,则会直接走对应的拼音纠错召回结果,若是不能经过拼音解决,再走编辑距离召回,解决是否漏字母的状况;如果部分中文或全中文的 query,则先进行拼音纠错,解决同音错别字问题,若无召回,则先进行分词,将先后相邻 term 拼接在一块儿进行拼音和编辑距离的召回。
还有不少流行的编辑距离算法,他们和Levenshtein distance算法不一样是使用了不一样种类的方式去变换字符串
编辑距离一般定义为使用一组特定容许的编辑操做来计算的可参数化度量,并为每一个操做分配成本(多是无限的)
这种算法实现比较简单,就是根据上述介绍的上下界限就能够得出逻辑了
//实现方法
private static int distance(String a, int len_a, String b, int len_b) {
//递归回归点
if (len_a == 0)
return len_b;
if (len_b == 0)
return len_a;
int cos;
if (a.charAt(len_a-1) == b.charAt(len_b-1))
cos = 0;
else
cos = 1;
int re1 = distance(a, len_a - 1, b, len_b) + 1;
int re2 = distance(a, len_a, b, len_b - 1) + 1;
int re3 = distance(a, len_a - 1, b, len_b - 1) + cos;
//返回在a中删除一个字符、在b中删除一个字符、ab中均删除一个字符得到结果中取最小值
return re1 < re2 ? (re1 < re3 ? re1 : re3) : (re2 < re3 ? re2 : re3);
}
//测试
public static void main(String[] args) {
String a = "kitten";
String b = "sitting";
int re = distnace(a, a.length(), b, b.length());
System.out.println(re);
//输出:3
}
复制代码
这种方式时间复杂度比较高,效率比较低,重复计算了好多字符串,下面采用动态规划算法实现。
算法原理部分就借用https://www.cnblogs.com/sumuncle/p/5632032.html 博主的部分文章吧=.=
算法基本原理:假设咱们可使用d[ i , j ]个步骤(可使用一个二维数组保存这个值),表示将串s[ 1…i ] 转换为 串t [ 1…j ]所须要的最少步骤个数
那么,在最基本的状况下,即在i等于0时,也就是说串s为空,那么对应的d[0,j] 就是 增长j个字符,使得s转化为t,在j等于0时,也就是说串t为空,那么对应的d[i,0] 就是 减小 i个字符,使得s转化为t。
而后咱们考虑通常状况,加一点动态规划的想法,咱们要想获得将s[1..i]通过最少次数的增长,删除,或者替换操做就转变为t[1..j],那么咱们就必须在以前能够以最少次数的增长,删除,或者替换操做,使得如今串s和串t只须要再作一次操做或者不作就能够完成s[1..i]到t[1..j]的转换。所谓的“以前”分为下面三种状况:
针对第1种状况,咱们只须要在最后将 t[j] 加上s[1..i]就完成了匹配,这样总共就须要k+1个操做。 针对第2种状况,咱们只须要在最后将s[i]移除,而后再作这k个操做,因此总共须要k+1个操做。 针对第3种状况,咱们只须要在最后将s[i]替换为 t[j],使得知足s[1..i] == t[1..j],这样总共也须要k+1个操做。而若是在第3种状况下,s[i]恰好等于t[j],那咱们就能够仅仅使用k个操做就完成这个过程。
最后,为了保证获得的操做次数老是最少的,咱们能够从上面三种状况中选择消耗最少的一种最为将s[1..i]转换为t[1..j]所须要的最小操做次数。 算法基本步骤:
private static int distance(String a, String b) {
int[][] dis = new int[a.length()+1][b.length()+1];
for (int i = 1; i <= a.length(); i++)
dis[i][0] = i;
for (int j = 1; j <= b.length(); j++)
dis[0][j] = j;
int cas;
for (int j = 1; j <= b.length(); j++) {
for (int i = 1; i <= a.length(); i++) {
if (a.charAt(i-1) == b.charAt(j-1))
cas = 0;
else
cas = 1;
int re = Math.min(dis[i - 1][j] + 1, dis[i][j - 1] + 1);
dis[i][j] = Math.min(re, dis[i - 1][j - 1] + cas);
}
}
return dis[a.length() - 1][b.length() - 1];
}
public static void main(String[] args) {
String a = "kitten";
String b = "sitting";
int re = distance(a, b);
System.out.println(re);
//输出:3
}
复制代码
若是转载此博文,请附上本文连接,谢谢合做~ :juejin.im/user/5c3036…
若是感受这篇文章对您有所帮助,请点击一下“喜欢”或者“关注”博主,您的喜欢和关注将是我前进的最大动力!