算法之美——求解 字符串间最短距离(动态规划)

Minimum Edit Distance 问题

 

解法一:

对于不同的字符串,判断其相似度。

          定义了一套操作方法来把两个不相同的字符串变得相同,具体的操作方法为:

  1.修改一个字符(如把“a”替换为“b”)

  2.增加一个字符(如把“abdd”变为“aebdd”)

  3.删除一个字符(如把“travelling”变为“traveling”)

   定义:把这个操作所需要的最少次数定义为两个字符串的距离,而相似度等于“距离+1”的倒数

采用递归的思想将问题转化成规模较小的同样的问题。

u     如果两个串的 第一个字符相同 ,如A=xabcdae和B=xfdfa,只要计算

         A[2,…,7]=abcdae和B[2,…,5]=fdfa的距离就可以了。

u     如果两个串的 第一个字符不相同 ,那么可以进行如下的操作:

    1.删除A串的第一个字符,然后计算A[2,…,lenA]和B[1,…,lenB]的距离。

    2.删除B串的第一个字符,然后计算A[1,…,lenA]和B[2,…,lenB]的距离。

    3.修改A串的第一个字符为B串的第一个字符,然后计算A[2,…,lenA]和

          B[2,…,lenB]的距离。

    4.修改B串的第一个字符为A串的第一个字符,然后计算A[2,…,lenA]和

          B[2,…,lenB]的距离。

    5.增加B串的第一个字符到A串的第一个字符之前,然后计算

          A[1,…,lenA]和B[2,…,lenB]的距离。

    6.增加A串的第一个字符到B串的第一个字符之前,然后计算

          A[2,…,lenA]和B[1,…,lenB]的距离。

  

我们并不在乎两个字符串变得相等之后的字符串是怎样的

可以将上面6个操作合并为:

1.一步操作之后,再将A[2,…,lenA]和B[1,…,lenB]变成相同字符串。

2.一步操作之后,再将A[1,…,lenA]和B[2,…,lenB]变成相同字符串。

3.一步操作之后,再将A[2,…,lenA]和B[2,…,lenB]变成相同字符串。

 伪代码

int calculateStringDistance(string strA, int pABegin, int pAEnd, string strB, int pBBegin, int pBEnd)

{
 if(pABegin > pAEnd)                                 //递归终止条件
 {
  if(pBBegin > pBEnd)     return 0;
 else  return pBEnd - pBBegin + 1;
 }

 if(pBBegin > pBEnd)
{
  if(pABegin > pAEnd)   return 0;
 else  return pAEnd - pABegin + 1;
}

    if(strA[pABegin] == strB[pBBegin])           //算法核心

   {
  return calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);
 }
else
 {
   int t1 = calculateStringDistance(strA, pABegin, pAEnd, strB, pBBegin+1, pBEnd);
   int t2 = calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin, pBEnd);
   int t3 = calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);
   return minValue(t1, t2, t3) + 1;
 }

 } 


简洁版

#define MAX 100
char s1[MAX];
char s2[MAX];

int distance(char *s1,char *s2)     //求字符串距离
{   int len1=strlen(s1);
    int len2=strlen(s2); 

    if(len1==0||len2==0) 
    { 
       return max(len1,len2); 
     }

      if(s1[0]==s2[0])      return distance(s1+1,s2+1);
      else    return min(distance(s1,s2+1),distance(s1+1,s2),distance(s1+1,s2+1))+1;
}

上面的算法有什么地方需要改进呢?

算法中,有些数据被重复计算。


为了避免这种重复计算,我们可以考虑将子问题计算后的解保存起来

 

 

动态规划 求解

第一部分讲解:http://blog.csdn.net/huaweidong2011/article/details/7727482


本篇内容将讲述Edit Distance(编辑距离的定义详见正文),具体又包含5个方面的内容:
  1. Defining Minimum Edit Distance 
  2. Computing Minimum Edit Distance
  3. Backtrace for Computing Alignments
  4. Weighted Minimum Edit Distance
  5. Minimum Edit Distance in Computational Biololgy

1. Definition of  Minimum Edit Distance 

Edit Distance用于衡量两个strings之间的相似性。
两个strings之间的 Minimum edit distance是指把其中一个string通过编辑(包括插入,删除,替换操作)转换为另一个string的最小操作数。
如上图所示,d(deletion)代表删除操作,s(substitution)代表替换操作,i(insertion)代表插入操作。
(为了简单起见,后面的Edit Distance 简写为ED)
如果每种操作的cost(成本)为1,那么ED = 5.
如果s操作的cost为2(即所谓的Levenshtein Distance),ED = 8.

2. Computing Minimum Edit Distance

那么如何找到两个strings的minimun edit distance呢?要知道把一个string转换为另一个string可以有很多种方法(或者说“路径“)。我们所知道起始状态(第一个string)、终止状态(另一个string)、基本操作(插入、删除、替换),要求的是最短路径。
对于如下两个strings:
X的长度为n
Y的长度为m
我们定义D(i,j)为 X 的前i个字符 X[1...i] 与 Y 的前j个字符 Y[1...j] 之间的距离,其中0<i<n, 0<j<m,因此X与Y的距离可以用D(n,m)来表示。
假如我们想要计算最终的D(n,m),那么可以从头开始,先计算D(i, j) (i和j从1开始)的值,然后基于前面的结果计算更大的D(i, j),直到最终求得D(n,m)。
算法过程如下图所示:

上图中使用的是”Levenshtein Distance“即替换的成本为2.
请读者深入理解一下上图中的循环体部分: D(i,j)可能的取值为:
1. D(i-1, j) +1 ;
2. D(i, j-1) +1 ;
3. D(i-1, j-1) + 2 (当X新增加的字符和Y新增加的字符不同时,需要替换)或者 + 0(即两个字符串新增加的字符相同)
下图即对字符串 INTENTION 和 EXECUTION 一步步求ED形成的表。左上角画红圈的8就是两个字符串间的最小ED。


3. Backtrace for Computing Alignments


上一节课我们求得了Edit distance,但是仅有Edit distance也是是不够的,有时我们也需要把两个strings中的每个字符都一一对应起来(有的字母会与“空白”对应),这可以通过Backtrace(追踪)ED的计算过程得到。
通过上一节我们知道,D(i, j)的取值来源有三种,D(i-1, j)、D(i, j-1)或者D(i-1, j-1),下表通过添加箭头的方式显而易见地给出来整个表格的计算过程(下面的阴影表示的只是一种路径,你会发现得到最后结果的路径不是惟一的,因为每个单元格数字可能由左边、下边或者左下边的得到)。

从表格右上角开始,沿着追踪的剪头,就可以拎出一条路径出来(不惟一),这条路径的剪头可以轻易的展现是通过哪种方法(插入、删除、替换)完成的。
表格右上角阴影部分四个格子,路径只有一条,我们也可以很轻易地看出最后四个字母是相同的,但这种情况并不绝对,比如中间的阴影6格也只有一种路径,可是却分别对应于字母e和c。
算法实现“寻找路径”的思想很简单——就是给每个单元格定义一个指针,指针的值为LEFT/DOWN/DIAG(不明白为什么他为什么说是指针),如下图所示。


想一下普通的情况,如下图,从(0,0)到(M,N)的任何一条非下降路径都对应于两个strings间的一个排列,而最佳的排列由最佳的子排列组成。

简单思考一下算法的性能
Time:    O(nm)
Space:  O(nm)
Backtrace: O(n+m)


4. Weighted Minimum Edit Distance

ED也可以添加权重,因为在拼写中,某些字母更容易写错。如下图显示的混淆矩阵,数值越大就代表被误写的可能性越高。如a就很可能被误写为e,i,o,u

众所周知,键盘排布会对误写产生影响。

Weighted Min Edit Distance的算法如下图所示

这幅图将del、ins、sub三种操作都定义了不同的权重,在“莱温斯基距离“中,del和ins的cost都是1,sub是2。

5. Minimum Edit Distance in Computational Biology

本段讲述Minimum Edit Distance在计算生物学中的应用。比如比较如下图(上班部分)两个基因组序列,我们希望最后能把两个序列对齐(下半部分),进而研究不同的基因片段的功能等。

在Natural Language Processing我们讨论了最小distance和weight,而在Computational Biology中我们将要介绍最大Similarity(相似性)和scores。
在Computational Biology中有个重要算法——Needleman-Wunsch算法。

 

第二部分 代码:http://blog.csdn.net/abcjennifer/article/details/7735272

自然语言处理(NLP)中,有一个基本问题就是求两个字符串的minimal Edit Distance, 也称Levenshtein distance。受到一篇Edit Distance介绍文章的启发,本文用动态规划求取了两个字符串之间的minimal Edit Distance. 动态规划方程将在下文进行讲解。 


1. what is minimal edit distance?

简单地说,就是仅通过插入(insert)、删除(delete)和替换(substitute)个操作将一个字符串s1变换到另一个字符串s2的最少步骤数。熟悉算法的同学很容易知道这是个动态规划问题。 

其实一个替换操作可以相当于一个delete+一个insert,所以我们将权值定义如下:

I  (insert):1

D (delete):1

S (substitute):2


2. example:

intention->execution

Minimal edit distance:

delete i ; n->e ; t->e ; insert c ; n->u 求和得cost=8

3.calculate minimal edit distance dynamically
思路见注释,这里D[i,j]就是取s1前i个character和s2前j个character所得minimal edit distance

三个操作动态进行更新:

D(i,j)=min { D(i-1, j) +1, D(i, j-1) +1 , D(i-1, j-1) + s1[i]==s2[j] ? 0 : 2};中的三项分别对应D,I,S。


/*
 * minEditDis.cpp
 *
 *  @Created on: Jul 10, 2012
 *      @Author: sophia
 *  @Discription: calculate the minimal edit distance between 2 strings
 *
 *  Method : DP (dynamic programming)
 *  D[i,j]: the minimal edit distance for s1的前i个字符和 s2的前j个字符
 *  DP Formulation: D[i,j]=min(D[i-1,j]+1,D[i,j-1]+1,D[i-1,j-1]+flag);//其中if(s1[i]!=s2[j])则flag=2,else flag=0;
 *
 */

#include"iostream"
#include"stdio.h"
#include"string.h"
using namespace std;

#define N 100
#define INF 100000000
#define min(a,b) a<b?a:b

int dis[N][N];
char s1[N],s2[N];
int n,m;//length of the two string


int main()
{
	int i,j,k;
	while(scanf("%s%s",&s1,&s2)!=EOF)
	{
		n=strlen(s1);m=strlen(s2);
		for(i=0;i<=n+1;i++)
			for(j=0;j<=m+1;j++)
				dis[i][j]=INF;
		dis[0][0]=0;

		for(i=0;i<=n;i++)
			for(j=0;j<=m;j++)
			{
				if(i>0) dis[i][j] = min(dis[i][j],dis[i-1][j]+1); //delete
				if(j>0) dis[i][j] = min(dis[i][j],dis[i][j-1]+1);//insert

				//substitute
				if(i>0&&j>0)
				{
					if(s1[i-1]!=s2[j-1])
						dis[i][j] = min(dis[i][j],dis[i-1][j-1]+2);
					else
						dis[i][j] = min(dis[i][j],dis[i-1][j-1]);
				}
			}

		printf("min edit distance is: %d\n",dis[n][m]);
	}
	return 0;
}


运行结果:


intention
execution
min edit distance is: 8
abc
acbfbcd
min edit distance is: 4
zrqsophia
aihposqrz
min edit distance is: 16


Reference: 

1. https://www.coursera.org/course/nlp

2. http://blog.csdn.net/huaweidong2011/article/details/7727482

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值