精华内容
下载资源
问答
  • 编辑距离

    万次阅读 多人点赞 2015-12-12 10:48:13
    1. 编辑距离定义  今天我们来研究一个有趣的算法题,叫做字符串编辑距离编辑距离研究的问题和最长公共子序列有相似之处,都是比较两个字符串之间的相似性,只是采用的标准不太相同。  先给出编辑距离的定义:设A...

    1. 编辑距离定义

           今天我们来研究一个有趣的算法题,叫做字符串编辑距离。编辑距离研究的问题和最长公共子序列有相似之处,都是比较两个字符串之间的相似性,只是采用的标准不太相同。

           先给出编辑距离的定义:设A和B是2个字符串,要用最少的字符操作将字符串A转换为字符串B。这里所说的字符操作包括:

           (1)删除一个字符(delete);

           (2)插入一个字符(insert);

           (3)将一个字符改为另一个字符(substitute)。

           将字符串A变换为字符串B所用的最少字符操作数称为字符串A到B的编辑距离(edit distance)。通常情况下,三种操作的代价是一样的,也即每种字符操作都会导致一次变换,这也符合我们平常的认知。但是,有时也可以对这三种操作施加不同的影响因子,使算法倾向于某种变换,可以通过修改三种操作的代价来实现。

           事实上,也可以包含更多的字符操作。例如,在英文输入的过程中经常会出现不小心交换两个字符的问题(trueàture),所以可以增加一个新的操作—置换(transposition),表示互换相邻的两个字符,此时编辑距离升级为Damerau–Levenshteindistance距离,它包含插入、删除、替换和置换四种操作。在OCR应用中还存在其他操作:将两个字符合并(merge)成一个或者将一个字符展开(split)成两个。当然,也可以减少字符操作,例如求两个等长字符串的汉明距离只用到了替换操作;还有,在求最长公共子序列(LCS)的问题中,其实等价于我们只采用插入和删除两种操作,虽然在LCS问题中求的是公共序列,我们也可以求它们之间的简化版编辑距离。

    2. 编辑距离性质

           编辑距离具有下面几个性质:

    • 两个字符串的最小编辑距离至少是两个字符串的长度差;
    • 两个字符串的最大编辑距离至多是两字符串中较长字符串的长度;
    • 两个字符串的编辑距离是零的充要条件是两个字符串相同;
    • 如果两个字符串等长,编辑距离的上限是汉明距离(Hamming distance);
    • 编辑距离满足三角不等式,即d(ac) ≤ d(ab) + d(bc)
    • 如果两个字符串有相同的前缀或后缀,则去掉相同的前缀或后缀对编辑距离没有影响,其他位置不能随意删除。

    3. 编辑距离的应用

           编辑距离应用很广,最初的应用是拼写检查和近似字符串匹配。在生物医学领域,科学家将DNA看成有A,S,G,T构成的字符串,然后采用编辑距离判断不同DNA的相似度。关于近似字符串匹配,LCS也是一个不错的选择,在不同的问题中都有自己的用武之地。

    编辑距离另一个很好的用途在语音识别中,它被当作一个评测指标。语音测试集的每一句话都有一个标准答案,然后利用编辑距离判断识别结果和标准答案之间的不同。不同的错误可以反映识别系统存在的问题。假如识别结果中插入错误较多,表示识别结果丢字很多,一个可能的原因是VAD做得不好;又或者识别结果中替换错误较多,则很有可能是训练过程中的语言模型不好。

    再看一个应用,在linux下我们经常会用diff命令或者vimdiff命令按行比较两个文件的不同。运行命令之后会展示出两个文件的不同,展示方式其实就是按照编辑距离来定义的:哪个位置插入多少行或者删除多少行或者替换哪几行。所以我们可以用编辑距离来实现diff命令。

    3. 算法实现

           该问题和LCS一样,也是利用动态规划来解决。定义dij表示长度为i的字符串a变为长度为j的字符串b需要的编辑距离。需要注意的一点是,如果字符串a的最终长度为m,字符串b的最终长度为n,则d矩阵是一个(m+1)*(n+1)的矩阵,因为它可以表示长度为0的字符串之间的转换。它的递推式满足:


    我们解释一下上面的公式,前两行是初始化,分别表示字符串b和a为空时对应的编辑距离计算。当字符串b为空时,我们将a变为b只需要不停地删除字符即可,反之则不停地添加字符。初始化完矩阵d的最初一行和一列之后我们就可以按照第三行公式去计算矩阵的剩余元素。第(i,j)个元素在计算的时候依赖于和它相邻的三个位置 (i-1,j-1)、(i,j-1)和(i-1,j)。公式的整体结构和LCS,DTW都非常像,唯一存在的难点就是到底哪一个位置对应删除操作,哪一个位置对应插入操作。由公式可以看出,(i-1,j)对应删除操作,(i,j-1)对应插入操作。可以这样理解,现在耗费了di-1,j步操作将字符串a(1,i-1)转换成了b(1,j),则在将a(1,i)转换成b(1,j)时,我们可以直接删掉字符a(i),问题变成a(1,i-1)转换成b(1,j),从而dij就等于di-1,j+1。同理,现在耗费了di,j-1步操作将字符串a(1,i)转换成了b(1,j-1),则在将a(1,i)转换成b(1,j)时,我们可以将b(j)添加到a(1,i)末尾(此时a(1,i)已转换成b(1,j-1))构成b(1,j)对应的代码实现如下:

    /*
    * 计算a->b的编辑距离
    * dist[i][j]表示长度为i的字符串变为长度为j的字符串需要的编辑距离
    * operation[i][j]表示变换过程中对应的操作
    * 0:正确;1:字符替换;2:插入;3:删除
    */int edit_distance(char* a,char* b)
    {
    	int len_a=strlen(a);
    	int len_b=strlen(b);
    
    	//b为空字符串,将a变为b需要不停地删除a的字符
    	for (int i=0;i<=len_a;i++)
    	{
    		dist[i][0]=i;
    		operation[i][0]=3;
    	}
    
    	//a为空字符串,将a变为b需要不停地添加b的字符
    	for (int j=0;j<=len_b;j++)
    	{
    		dist[0][j]=j;
    		operation[0][j]=2;
    	}
    
    	operation[0][0]=0;
    
    	for (int i=1;i<=len_a;i++)
    	{
    		for (int j=1;j<=len_b;j++)
    		{
    			int cost = (a[i-1] == b[j-1] ? 0 : 1);   
    
    			int deletion = dist[i-1][j] + 1;   
    			int insertion = dist[i][j-1] + 1;   
    			int substitution = dist[i-1][j-1] + cost;   
    
    			//如果不回溯直接利用下面这句
    			//dist[i][j] = MIN(deletion,MIN(insertion,substitution)); 
    			if(deletion>insertion)
    			{
    				if(substitution>insertion)
    				{
    					dist[i][j]=insertion;
    					operation[i][j]=2;
    				}
    				else
    				{
    					dist[i][j]=substitution;
    					operation[i][j]=cost;
    				}
    			}
    			else
    			{
    				if(substitution>deletion)
    				{
    					dist[i][j]=deletion;
    					operation[i][j]=3;
    				}
    				else
    				{
    					dist[i][j]=substitution;
    					operation[i][j]=cost;
    				}
    			}
    		}
    	}
    
    	return dist[len_a][len_b];
    }
    

    很多情况下,我们并不单纯地求编辑距离,还要把具体的操作步骤求出来,这可以通过回溯上面代码计算出的operation矩阵完成,回溯代码如下:

    void backtrace(int operation[][MAX_LEN+1],char* a,char* b)
    {
    	int insertion=0,deletion=0,substitution=0;
    	int i,j;
    	int len1=strlen(a);
    	int len2=strlen(b);
    
    	for (i=len1,j=len2;i>=0&&j>=0;)
    	{
    		switch(operation[i][j])
    		{
    		case 0:
    			//printf("(%d,%d) right\n",i,j);
    			printf("pos %d right\n",i);
    			i--;
    			j--;
    			continue;
    		case 1:
    			//printf("(%d,%d) substitute\n",i,j);
    			printf("pos %d substitute (%c-->%c)\n",i,a[i-1],b[j-1]);
    			i--;
    			j--;
    			substitution++;
    			continue;
    		case 2:
    			//printf("(%d,%d) insert\n",i,j);
    			printf("pos %d insert (%c)\n",i,b[j-1]);
    
    			j--;
    			insertion++;
    			continue;
    		case 3:
    			//printf("(%d,%d) delete\n",i,j);
    			printf("pos %d delete (%c)\n",i,a[i-1]);
    			i--;
    			deletion++;
    			continue;
    		}
    	}
    
    	printf("insert:%d,delete:%d,substitute:%d\n",insertion,deletion,substitution);
    }
    
    大家可以测试上面的代码,也可以根据上述代码去自行扩展,比如实现diff命令或者实现扩展的编辑距离等。事实上,对Damerau–Levenshteindistance距离的扩展在明白了基础的编辑距离之后也非常简单,无非是增加了一种置换操作而已,递推式如下:

    编辑距离作为一个经典的字符串动态规划题目,具有非常重要的应用,希望大家能熟练掌握。

    展开全文
  • 最小编辑距离的那几个题

    一、前言

      如果一块表走的不准,那它的每一秒都是错的,但如果这块表停了,那它起码每天有两次是对的;
      没错,清醒的停留胜过盲目的前行!
      及时回顾下自己这段时间学过的东西,并且加以总结,精益求精,总比学了一大堆,然后忘记在灯火阑珊处要好得多!

    二、最小编辑距离的定义

    • 编辑距离是对两个字符串(或者序列)的差异化度量,看至少需要多少次的操作才能将一个字符串变成另一个字符串。我们一般所说的编辑距离就是指最小编辑距离。
    • 编辑距离可以应用于自然语言处理中,比如在拼写检查时可以根据一个拼错的词和其他正确的词的编辑距离大小,来判断哪个是比较可能的词。或者应用于生物学的 DNA 的相似程度判定、两段文本内容的 diff 等等。
    • 最小编辑距离和之前提到的最长公共子序列的问题有着异曲同工之妙,状态转移方程也比较类似。

    1、莱文斯坦距离

    • 莱文斯坦距离,即 Levenshtein Distance,是指对于两个字符串(源字符串和目标字符串),由源字符串转变成目标字符串所需的最少编辑次数。允许的编辑操作包括以下三种:
    • 1)Replace:将源字符串中的一个字符替换成另一个字符;
    • 2)Insert:在源字符串中插入一个字符;
    • 3)Delete:在源字符串中删除一个字符;

    2、达梅劳-莱文斯坦距离

    • 达梅劳-莱文斯坦距离,即 Damerau–Levenshtein Distance,除了莱文斯坦距离的三种操作外,还增加了相邻两个字符的交换;

    3、LCS 距离

    • LCS 距离,只允许两种操作,相比莱文斯坦距离,少了替换这步操作:
    • 1)Insert:在源字符串中插入一个字符;
    • 2)Delete:在源字符串中删除一个字符;

    4、汉明距离

    • 汉明距离,只允许一步操作,即:
    • 1)Replace:将源字符串中的一个字符替换成另一个字符;
    • 汉明距离是使用在数据传输差错控制编码中的,它表示两个相同长度的串,对应位不同的数量。以二进制串为例,对两个二进制串进行异或运算,并统计串中结果为1的个数,这个数就是汉明距离。

    三、最小编辑距离的求解

    • 上面的一些概念可能比较模糊,我们通过一个例题来加深理解。

    【例题1】长度为 nn 的源字符串 a1,a2,...,ana_1,a_2,...,a_n,经过一些给定操作变成长度为 mm 的目标字符串 b1,b2,...bmb_1,b_2,...b_m,操作包括如下三种:
      1)InsertInsert:在源字符串中插入一个字符,插入消耗为 II
      2)DeleteDelete:在源字符串中删除一个字符,删除消耗为 DD
      3)ReplaceReplace:将源字符串中的一个字符替换成另一个字符,替换消耗为 RR
    求最少的总消耗,其中 n,m1000n,m \le 1000

    • 这个问题将莱文斯坦距离加上了权值,替换、插入、删除三种操作的消耗不再是 1,而是三个不一定相同的常数 R,I,DR, I, D

    1、设计状态

    • f[i][j]f[i][j] 表示源字符串 a1,a2,...,aia_1,a_2,...,a_i 经过上述三种操作变成目标字符串 b1,b2,...bjb_1,b_2,...b_j 的最少消耗。

    1)插入

    • 假设 a1,a2,...,aia_1,a_2,...,a_{i} 变成 b1,b2,...bj1b_1,b_2,...b_{j-1} 的最少消耗已经求出,等于 f[i][j1]f[i][j-1],则需要在 a[i]a[i] 的后面插入一个字符 bjb_j,那么产生的消耗为:
    • f[i][j1]+If[i][j-1] + I
    • 如图三-1-1所示,源字符串为 “AGTA”,目标字符串为 “GATCGT” 的情况下,将源字符串变成 "“GATCG” 的最小消耗为 f[i][j1]f[i][j-1],那么只要在源字符串最后再插入一个 ‘T’,就可以把源字符串变成目标字符串 “GATCGT”;
      在这里插入图片描述
    图三-1-1

    2)删除

    • 假设 a1,a2,...,ai1a_1,a_2,...,a_{i-1} 变成 b1,b2,...bjb_1,b_2,...b_{j} 的最少消耗已经求出,等于 f[i1][j]f[i-1][j],则需要把 aia_i 个删掉,那么产生的消耗为:
    • f[i1][j]+Df[i-1][j] + D
    • 如图三-1-2所示,源字符串为 “AGTA”,目标字符串为 “GATCGT” 的情况下,将 “AGT” 变成目标字符串的最小消耗为 f[i1][j]f[i-1][j],那么只要把源字符串最后一个’A’删掉,就可以把源字符串变成目标字符串;
      在这里插入图片描述
    图三-1-2

    3)替换

    • 假设 a1,a2,...,ai1a_1,a_2,...,a_{i-1} 变成 b1,b2,...bj1b_1,b_2,...b_{j-1} 的最少消耗已经求出,等于 f[i1][j1]f[i-1][j-1],则将 aia_i 替换成 bjb_ja1,a2,...,aia_1,a_2,...,a_{i} 就可以变成 b1,b2,...bjb_1,b_2,...b_{j}。替换时需要考虑 ai=bja_i=b_jaibja_i \neq b_j 的情况,所以替换产生的消耗为:
    • f[i1][j1]+{0ai=bjRaibjf[i-1][j-1] + \begin{cases} 0 & a_i=b_j \\ R & a_i \neq b_j\end{cases}
    • 如图三-1-3所示,源字符串为 “AGTA”,目标字符串为 “GATCGT” 的情况下,将 “AGT” 变成 “GATCGT” 的最小消耗为 f[i1][j1]f[i-1][j-1],那么只要将 源字符串 的最后一个字符 替换为 目标字符串 的最后一个字符 ,就可以把源字符串变成目标字符串;替换时根据 源字符串 和 目标字符串 原本是否相等来决定消耗;

    在这里插入图片描述

    图三-1-3

    4)边界处理

    • 边界情况主要考虑以下几种:

    a. 空串变成目标串

    • f[0][j]f[0][j],总共需要插入 jj 个字符,所以 f[0][j]=f[0][j1]+If[0][j] = f[0][j-1] + I

    b. 源字符串变成空串

    • f[i][0]f[i][0],总共需要删除 ii 个字符,所以 f[i][0]=f[i1][0]+Df[i][0] = f[i-1][0] + D

    c. 空串变成空串

    • f[0][0]=0f[0][0] = 0

    2、状态转移方程

    • 将上述所有状态进行一个整合,得到状态转移方程如下:
    • f[i][j]={0i=0,j=0f[i][j1]+Ii=0,j>0f[i1][j]+Di>0,j=0mini>0,j>0{f[i][j1]+If[i1][j]+Df[i1][j1]+Raibjf[i][j] = \begin{cases}0 & i=0,j=0\\f[i][j-1]+I & i=0,j>0\\ f[i-1][j] + D & i>0,j=0 \\ \min_{i>0,j>0} \begin{cases} f[i][j-1] + I\\ f[i-1][j] + D\\ f[i-1][j-1] + R_{a_i \neq b_j}\end{cases}\end{cases}
    • 通过这个状态矩阵,最后计算得到 f[n][m]f[n][m] 就是该题所求 "源字符串 a1,a2,...,ana_1,a_2,...,a_n,经过 插入、删除、替换 变成目标字符串 b1,b2,...bmb_1,b_2,...b_m" 的最少消耗了,特殊的,当 I=D=R=1I = D = R = 1 时,f[n][m]f[n][m] 就是字符串 aabb 的莱文斯坦距离。

    3、时间复杂度分析

    • 状态总数 O(nm)O(nm),每次状态转移的消耗为 O(1)O(1),所以总的时间复杂度为 O(nm)O(nm),空间上可以采用滚动数组进行优化,具体优化方案可以参考 最长公共子序列 的求解过程。

    4、莱文斯坦距离图

    • 图三-4-1所示的是一张源字符串 “AGTACG” 到目标字符串 “GATCGTGAGC” 的莱文斯坦距离图。
      在这里插入图片描述
    图三-4-1

    四、最小编辑距离的扩展

    1、限定次数

    【例题2】长度为 nn 的源字符串 a1,a2,...,ana_1,a_2,...,a_n,经过一些给定操作变成目标字符串 b1,b2,...bmb_1,b_2,...b_m 的子串,操作包括如下三种:
      1)InsertInsert:在源字符串中插入一个字符;
      2)DeleteDelete:在源字符串中删除一个字符;
      3)ReplaceReplace:将源字符串中的一个字符替换成另一个字符;
    求最少的操作次数,其中 n103,m106n \le 10^3, m \le 10^6,操作次数限制在 l(l30)l(l \le 30) 次以内,无法完成输出 -1。

    • 这个问题的源串长度最大为 10310^3,而目标串长度最大为 10610^6,如果采用暴力计算莱文斯坦距离的话,O(nm)O(nm) 的时间复杂度肯定是不行的。所以需要采用一些优化,和普通的计算莱文斯坦距离的问题有两个不同点:
    • 1)最后结果是要变成目标串的子串;
    • 2)限定了操作次数不能超过 ll 次;
    • 定义 f[i][j]f[i][j]a1,a2,...,aia_1,a_2,...,a_i 经过以上三种操作变成 b1,b2,...bjb_1,b_2,...b_j 后缀的最少次数。
    • 对于第 1)个问题,可以这么来考虑,因为空串是任何串的子串,所以由空串变成目标串的后缀的最少操作次数为零,即:
    • f[0][j]=0f[0][j] = 0
    • 对于第 2)个问题,我们考虑下将源串变成空串的操作次数,显然只能通过删除字符来完成,也就是长度为 ii 的源串,至少需要 ii 次 操作才能变成空串,即:f[i][0]=if[i][0] = i
    • 这时候我们发现 f[i][0]>limf[i][0] > lim 的情况是非法状态,因为它们超过了限定次数,也就是当目标串为空串时,ii 的范围是 [0,lim][0, lim];那么,我们可以知道,当目标串长度为 jj,如果满足 f[i][j]limf[i][j] \le lim 的最大的 ii 等于 pp 时,当目标串长度增加 1,ii 的值不会超过 p+1p+1, 这样就可以限制 ii 的范围了。
    • 实际进行状态计算的时候,jj 放外层枚举,并且采用滚动数组,ii 放内层枚举,并且限定范围在 [0,p+1][0, p+1],枚举完毕根据 f[p][j]limf[p][j] \le lim 这个条件更新 pp 的范围。
    • 如图四-1-1所示,纵向为源串,横向为目标串,lim=2lim = 2,采用列优先的方式进行枚举计算,当前列计算完毕后,红色状态为非法状态,即 f[i][j]>2f[i][j] > 2 的状态。图中的数字为对应 f[i][j]f[i][j] 的值。
    图四-1-1

    2、路径回溯

    【例题3】长度为 nn 的源字符串 a1,a2,...,ana_1,a_2,...,a_n,经过一些给定操作变成目标字符串 b1,b2,...bmb_1,b_2,...b_m,操作包括如下三种:
      1)InsertInsert:在源字符串中插入一个字符;
      2)DeleteDelete:在源字符串中删除一个字符;
      3)ReplaceReplace:将源字符串中的一个字符替换成另一个字符;
    求最少的操作次数,并且输出任意一种完整的执行方案。

    • 在讲解背包问题和最长公共子序列问题的时候都已经详细讲过路径回溯的问题了,那么求解最小编辑距离的时候也一样,用一个前驱数组 p[i][j]p[i][j] 记录下状态转移的方向,然后根据状态转移的方向来确定是插入、删除还是替换。

    • 关于 最小编辑距离 的内容到这里就结束了。
    • 如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。


    在这里插入图片描述


    五、最小编辑距离相关题集整理

    题目链接 难度 解析
    HDU 4323 Magic Number ★☆☆☆☆ Levenshtein 距离裸题
    PKU 3356 AGTC ★☆☆☆☆ Levenshtein 距离裸题
    NC35 最小编辑代价 ★☆☆☆☆ 【例题1】带权编辑距离
    HDU 1080 Human Gene Functions ★★☆☆☆ LCS 距离
    HDU 2895 Edit distance ★★☆☆☆ 贪心
    HDU 3540 EditingOperation ★★★☆☆ 【例题2】限定次数
    HDU 1516 String Distance and Transform Process ★★★☆☆ 【例题3】路径回溯
    HDU 4271 Find Black Hand ★★★☆☆ 暴力枚举 + DP
    展开全文
  • 编辑距离

    参考博文
    http://www.cnblogs.com/sking7/archive/2011/10/16/2214044.html
    http://www.cnblogs.com/biyeymyhjob/archive/2012/09/28/2707343.html

    编辑距离,又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。

    例如将kitten一字转成sitting:

    sitten (k→s)
    sittin (e→i)
    sitting (→g)

    首先定义这样一个函数——edit(i, j),它表示第一个字符串的长度为i的子串到第二个字符串的长度为j的子串的编辑距离。

    显然可以有如下动态规划公式:

    • if i == 0 且 j == 0,edit(i, j) = 0
    • if i == 0 且 j > 0,edit(i, j) = j
    • if i > 0 且j == 0,edit(i, j) = i
    • if i ≥ 1 且 j ≥ 1 ,edit(i, j) == min{ edit(i-1, j) + 1, edit(i, j-1) + 1, edit(i-1, j-1) + f(i, j) },当第一个字符串的第i个字符不等于第二个字符串的第j个字符时,f(i, j) = 1;否则,f(i, j) = 0。
    i/j 0 f a i l i n g
    0 0 1 2 3 4 5 6 7
    s 1 1 2 3 4 5 6 7
    a 2 2 1 2 3 4 5 6
    i 3 3 2 1 2 3 4 5
    l 4 4 3 2 1 2 3 4
    n 5 5 4 3 2 2 2 3
    展开全文
  • 编辑距离

    千次阅读 2017-12-26 15:50:08
    编辑距离是计算两个文本相似度的算法之一,以字符串为例,字符串a和字符串b的编辑距离是将a转换成b的最小操作次数,这里的操作包括三种: 插入一个字符 删除一个字符 替换一个字符 举个例子,kitten和sitting的...

    定义

    编辑距离又称Leveinshtein距离,是由俄罗斯科学家Vladimir Levenshtein在1965年提出。编辑距离是计算两个文本相似度的算法之一,以字符串为例,字符串a和字符串b的编辑距离是将a转换成b的最小操作次数,这里的操作包括三种:

    • 插入一个字符
    • 删除一个字符
    • 替换一个字符

    举个例子,kitten和sitting的编辑距离是3,kitten -> sitten(k替换为s) -> sittin(e替换为i) -> sitting(插入g),至少要做3次操作。

    实现

    leva,b(i,j)来表示a和b的Leveinshtein距离(i和j分别代表a和b的长度),则:

    1. min(i,j)=0leva,b(i,j)=max(i,j)0
    2. ai=bjleva,b(i,j)=leva,b(i1,j1)xxczxyz=xxcxy
    3. leva,b(i,j)
      • leva,b(i1,j)+1(ai)xxcxyz=xxxyz+1
      • leva,b(i,j1)+1(bj)xxcxyz=xxczxyz+1=xxcxy+1
      • leva,b(i1,j1)+1(bj)xxcxyz=xxzxyz+1=xxxy+1

    用公式表示如下:

    leva,b(i,j)=if min(i,j)=0 max(i,j)if ai=bjleva,b(i1,j1)otherwiseminleva,b(i1,j)+1leva,b(i,j1)+1leva,b(i1,j1)+1

    递归实现

    用上面的公式可以很容易的写出递归实现:

    public static int levenshteinDistance(String left, String right) {
        return levenshteinDistance(left.toCharArray(), left.length(), right.toCharArray(), right.length());
    }
    private static int levenshteinDistance(char[] left, int leftLen, char[] right, int rightLen) {
        if (Math.min(leftLen, rightLen) == 0) {
            return Math.max(leftLen, rightLen);
        }
        if (left[leftLen - 1] == right[rightLen - 1]) {
            return levenshteinDistance(left, leftLen - 1, right, rightLen - 1);
        }
        return Math.min(levenshteinDistance(left, leftLen - 1, right, rightLen),
                Math.min(levenshteinDistance(left, leftLen, right, rightLen - 1),
                        levenshteinDistance(left, leftLen - 1, right, rightLen - 1))) + 1;
    }

    递归的实现比较简单,递归的思想是通过递归的形式,最终得到一个由不可继续分割(递归出口)的式子组成的表达式,最终会存在非常多的重复的不可继续分割的式子,造成大量的重复计算,所以很低效。

    动态规划实现

    递归是从后向前分解,那与之相对的就是从前向后计算,逐渐推导出最终结果,此法被称之为动态规划,动态规划很适用于具有重叠计算性质的问题,但这个过程中会存储大量的中间计算的结果,一个好的动态规划算法会尽量减少空间复杂度。

    全矩阵

    以xxc和xyz为例,建立一个矩阵,通过矩阵记录计算好的距离:
    这里写图片描述
    min(i,j)=0leva,b(i,j)=max(i,j),根据此初始化矩阵的第一行和第一列:
    这里写图片描述
    依据上面的公式可以继续推导出第二行:
    这里写图片描述
    继续迭代,直至推导出最终结果:
    这里写图片描述
    这个过程记录了所有中间结果,空间复杂度为O(n2),来看一下代码实现:

    public static int levenshteinDistance(String left, String right) {
        // 创建矩阵
        int[][] d = new int[left.length() + 1][right.length() + 1];
        // 初始化第一列
        for (int i = 0; i <= left.length(); i++) {
            d[i][0] = i;
        }
        // 初始化第一行
        for (int j = 1; j <= right.length(); j++) {
            d[0][j] = j;
        }
        // 从第二行第二列开始迭代
        for (int i = 1; i <= left.length(); i++) {
            for (int j = 1; j <= right.length(); j++) {
                // 套公式计算
                if (left.charAt(i - 1) == right.charAt(j - 1)) {
                    d[i][j] = d[i - 1][j - 1];
                } else {
                    d[i][j] = Math.min(d[i - 1][j], Math.min(d[i][j - 1], d[i - 1][j - 1])) + 1;
                }
            }
        }
        // 最后一个格子即为最终结果
        return d[left.length()][right.length()];
    }

    两行

    空间复杂度可以继续优化,我们计算当前行时,只依赖上一行的数据,所以我们只需要O(2n)的空间复杂度,代码实现:

    public static int levenshteinDistance3(String left, String right) {
        int[] pre = new int[right.length() + 1];// 上一行
        int[] current = new int[right.length() + 1];// 当前行
        // 初始化第一行
        for (int i = 0; i < pre.length; i++) {
            pre[i] = i;
        }
        for (int i = 1; i <= left.length(); i++) {
            current[0] = i;// 第一列
            for (int j = 1; j <= right.length(); j++) {
                // 套公式计算
                if (left.charAt(i - 1) == right.charAt(j - 1)) {
                    current[j] = pre[j - 1];
                } else {
                    current[j] = Math.min(current[j - 1], Math.min(pre[j], pre[j - 1])) + 1;
                }
            }
            // current -> pre
            System.arraycopy(current, 0, pre, 0, current.length);
        }
        return pre[pre.length - 1];
    }

    单行

    我们还可以进一步优化,其实只需要一行就可以了,计算当前格子时,只需要左、上、左上的值,左面的值可以直接得到,上面的值是当前格子修改前的旧值,也可以直接得到,左上角的值是左面格子修改前的旧值,需要暂存,这时空间复杂度为O(n)
    这里写图片描述
    代码实现:

    public static int levenshteinDistance(String left, String right) {
        // 初始化当前行
        int[] d = new int[right.length() + 1];
        for (int i = 0; i < d.length; i++) {
            d[i] = i;
        }
        int leftTop, nextLeftTop;
        for (int i = 1; i <= left.length(); i++) {     
            leftTop = i - 1;// 当前行的左上角初始值
            d[0] = i;// 第一列
            for (int j = 1; j <= right.length(); j++) {
                nextLeftTop = d[j];// 暂存,此时d[j]为上一行的值,也是d[j+1]左上角的值
                // 套公式计算
                if (left.charAt(i - 1) == right.charAt(j - 1)) {
                    d[j] = leftTop;
                } else {
                    d[j] = Math.min(d[j - 1], Math.min(d[j], leftTop)) + 1;
                }
                leftTop = nextLeftTop;
            }
        }
        return d[d.length - 1];
    }

    应用

    编辑距离是基于文本自身去计算,没有办法深入到语义层面,可以胜任一些简单的分析场景,如拼写检查、抄袭侦测等,在我的工作中,该算法在数据聚合时有一定的运用。

    参考

    https://en.wikipedia.org/wiki/Levenshtein_distance
    http://www.dreamxu.com/books/dsa/dp/edit-distance.html


    版权声明
    本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者高爽和本文原始地址:http://blog.csdn.net/ghsau/article/details/78903076

    展开全文
  • 编辑距离编辑距离算法

    千次阅读 2016-04-24 11:19:00
    编辑距离概念描述:编辑距离,又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。 例如将kitten一字...
  • 基础算法:编辑距离

    千次阅读 多人点赞 2020-10-07 19:47:06
    编辑距离又称莱文斯坦距离,是俄国数学家Vladimir Levenshtein在1965年所提出的概念,主要用来判断对于两个字符串的差异化的量化,确认需要多少次处理才能将一个字符串变为另外一个,处理一般分为添加、删除和替换三...
  • 最小编辑距离

    千次阅读 2017-01-19 21:14:40
    编辑距离是一种衡量两个相似字符串相似性的度量方法。距离越大相似度越小。具体地,两个字符串的编辑距离是其中一个字符串要变换为另一个字符串所需要的最小编辑次数。其中编辑操作包含3种:增加一个字符,删除一个...
  • 编辑距离算法杂烩

    2018-03-26 14:12:22
    今天分享一下编辑距离的相关东西。定义 首先说一下 什么是编辑距离?在信息论、语言学、计算机科学中,编辑距离是一个测量两个序列之间差异的度量。通俗地说,编辑距离就是从字符串X转换到Y需要的插入、删除、替换...
  • 详解编辑距离算法-Levenshtein Distance

    万次阅读 2020-02-06 18:19:03
    •什么是编辑距离? •思路 •思路可视化 •代码实现 •写在前面 编辑距离算法被数据科学家广泛应用,是用作机器翻译和语音识别评价标准的基本算法。最简单的方法是检查所有可能的编辑序列,从中找出最短的一条...
  • 加一点自己理解 ...编辑距离,又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。 例如将kit
  • 编辑距离算法和Levenshtein距离算法

    千次阅读 2019-03-25 20:53:47
    各种diff工具的核心基本都是编辑距离算法,网上许多文章把编辑距离算法等同于Levenshtein距离算法,但实际上Levenshtein距离算法只是各种编辑距离算法其中之一。各种编辑距离算法会使用不同的编辑操作种类,例...
  • 编辑距离(Edit Distance)

    千次阅读 2020-03-17 19:41:41
    动态规划计算编辑距离
  • 最短编辑距离

    千次阅读 2017-07-12 17:59:35
    1.最短编辑距离的介绍 ①基本定义  所谓编辑距离(Edit Distance),是指两个字符串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作总共有三个:将一个字符替换成另一个字符、插入一个字符或者删除...
  • 编辑距离Edit distance

    千次阅读 2015-06-06 00:53:44
    http://blog.csdn.net/pipisorry/article/details/46383947编辑距离Edit distance-序列之间的距离我们知道,汉明距离可以度量两个长度...在这种场合下,通常使用更加复杂的编辑距离(Edit distance, Levenshtein dist
  • Java实现 LeetCode 72 编辑距离

    万次阅读 多人点赞 2020-02-16 15:17:08
    72. 编辑距离 给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作: 插入一个字符 删除一个字符 替换一个字符 示例 1: 输入: word1 = “horse”, ...
  • 使用编辑距离计算文本相似度

    千次阅读 2020-02-05 19:56:16
    3. 使用编辑距离计算文本相似度 3. 最小编辑距离计算文本相似度 3.1 编辑距离 概念: 通俗来讲,编辑距离Edit Distance(ED),是指将一个字符串转化为另一个字符串所需的最少操作数。操作包含以下几种: 增:增加...
  • 字符串编辑距离之LevenshteinDistance

    千次阅读 2018-08-04 16:42:37
    字符串编辑距离之LevenshteinDistance(EditDistance、LevensteinDistance )
  • 加权编辑距离

    千次阅读 2012-11-13 21:57:52
    在词项独立的矫正方法中,有一种叫做编辑距离的方法。 给定两个字符串s1和s2,两者的编辑距离定义为将s1转换成s2的最小编辑操作数。 这些编辑操作包括: 将一个字符插入字符串中将一个字符从字符串中删除将...
  • python实现编辑距离算法

    千次阅读 2017-12-27 20:24:36
    原文章链接:python实现计算最小编辑距离 最小编辑距离或莱文斯坦距离(Levenshtein),指由字符串A转化为字符串B的最小编辑次数。允许的编辑操作有:删除,插入,替换。具体内容可参见:Wikipedia - Levenshtein_...
  • 句子的编辑距离

    千次阅读 2015-06-24 15:02:24
    在机器翻译中,有时候要做句子的相似度比对,其中要用到编辑距离的计算。而网络上搜索到的资料大部分都将字符作为编辑距离计算的最小单位。事实上,对于句子来说,词语作为编辑距离的最小计算单位往往更加合理。通过...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 103,789
精华内容 41,515
关键字:

编辑距离