编辑距离 字符串相似度问题

编辑距离 
       关于两个字符串s1,s2的差别,可以通过计算他们的最小编辑距离来决定。 
       所谓的编辑距离: 让s1和s2变成相同字符串需要下面操作的最小次数。 
1.     把某个字符ch1变成ch2 
2. 删除某个字符 
3. 插入某个字符 
例如 s1 = “12433” 和s2=”1233”; 
则可以通过在s2中间插入4得到12433与s1一致。 
   即 d(s1,s2) = 1 (进行了一次插入操作) 
编辑距离的性质 
计算两个字符串s1+ch1, s2+ch2的编辑距离有这样的性质: 
1. d(s1,””) = d(“”,s1) = |s1|    d(“ch1”,”ch2”) = ch1 == ch2 ? 0 : 1; 
2. d(s1+ch1,s2+ch2) = min( d(s1,s2)+ ch1==ch2 ? 0 : 1 , 
d(s1+ch1,s2), 
d(s1,s2+ch2) ); 
第一个性质是显然的。 
第二个性质: 由于我们定义的三个操作来作为编辑距离的一种衡量方法。 
于是对ch1,ch2可能的操作只有 
1. 把ch1变成ch2 
2. s1+ch1后删除ch1 d = (1+d(s1,s2+ch2)) 
3. s1+ch1后插入ch2 d = (1 + d(s1+ch1,s2)) 
对于2和3的操作可以等价于: 
_2.   s2+ch2后添加ch1 d=(1+d(s1,s2+ch2)) 
_3.   s2+ch2后删除ch2 d=(1+d(s1+ch1,s2)) 
因此可以得到计算编辑距离的性质2。 
复杂度分析 
从上面性质2可以看出计算过程呈现这样的一种结构(假设各个层用当前计算的串长度标记,并假设两个串长度都为 n ) 
可以看到,该问题的复杂度为指数级别 3 的 n 次方,对于较长的串,时间上是无法让人忍受的。 
   分析: 在上面的结构中,我们发现多次出现了 (n-1,n-1), (n-1,n-2)……。换句话说该结构具有重叠子问题。再加上前面性质2所具有的最优子结构。符合动态规划算法基本要素。因此可以使用动态规划算法把复杂度降低到多项式级别。 
动态规划求解 
   首先为了避免重复计算子问题,添加两个辅助数组。 
一. 保存子问题结果。 
M[ |s1| ,|s2| ] , 其中M[ i , j ] 表示子串 s1(0->i) 与 s2(0->j) 的编辑距离 
二. 保存字符之间的编辑距离. 
E[ |s1|, |s2| ] , 其中 E[ i, j ] = s[i] = s[j] ? 0 : 1 
   三.   新的计算表达式 
根据性质1得到 
M[ 0,0] = 0; 
M[ s1i, 0 ] = |s1i|; 
M[ 0, s2j ] = |s2j|; 
根据性质2得到 
M[ i, j ]   = min( m[i-1,j-1] + E[ i, j ] , 
    m[i, j-1] , 
    m[i-1, j] ); 
   复杂度 
从新的计算式看出,计算过程为 
i=1 -> |s1| 
j=1 -> |s2| 
    M[i][j] = …… 
因此复杂度为 O( |s1| * |s2| ) ,如果假设他们的长度都为n,则复杂度为 O(n^2) 
--------------------------------------------------------------------------------------------------------------------------
/*C语言*/
/* 关于两个字符串s1,s2的差别,可以通过计算他们的最小编辑距离来决定。 
   所谓的编辑距离: 让s1和s2变成相同字符串需要下面操作的最小次数。 
1. 把某个字符ch1变成ch2 
2. 删除某个字符 
3. 插入某个字符 
*/
//#include <stdafx.h>
#include "stdio.h"
int min(int, int, int);
int d(char*, char*);
//int length(char*);
int main(int argc, char* argv[])
{char *a ="asfewagvea";
    char* b = "asddvsveskl";
    printf("result is   %d" ,d(a,b));
    printf("\n" );
    return 0;
}int min(int a, int b, int c)
{    int temp = (a < b ? a : b);
    return (temp < c? temp : c);
}/*int length(char* str)
{ int temp=0;
   while (*str !='\0'){
    temp++;
    str++;
} return temp;
}*/
int d(char* a, char* b)
{    // int m = length(a);
int m = strlen(a);
    // int n = length(b);
int n = strlen(b);
int i,j ,temp;
int **d;
d=(int **)malloc(sizeof(int *)*(m+1)); 
for(i=0;i<=m;i++){
    d[i]=(int *)malloc(sizeof(int)*(n+1)); 
}d[0][0]=0;
for(i=1;i<=m;i++){ d[i][0]=i; }
for(j=1;j<=n;j++){ d[0][j]=j; }
for( i=1;i<=m;i++){
   for(j=1;j<=n;j++){
    int edit=( a[i-1] == b[j-1] ? 0 : 1);
    d[i][j]=min((d[i-1][j-1]+edit),//更改或不变字符串
(d[i][j-1]+1),    //插入
(d[i-1][j]+1));   //删除
   }
}    
    temp = d[m][n];
    free(d);
return temp;
}//JAVA
public class EditDistance {
    public static void main(String args[]) {
   String x = "fxpimu";
   String y = "xwrs";
System.out.println(EditDistance.getEditDistance(x.toCharArray( ), y.toCharArray()));
    }
    //求三个数中最小的
    public static int min(int a, int b, int c)
    {
    int temp = (a < b ? a : b);
    return (temp < c? temp : c);
    }
    //计算两个字符串间的编辑距离
    public static int getEditDistance(char a[], char b[])
{   int i, j;
   int m = a.length;
    int n = b.length;
int d[] = new int[n + 1];
for(i = 0; i <= n; i++)
d[i] = i;
for(i = 1; i <= m; i++)
{
int y = i - 1;
for(j = 1; j <=n; j++)
{
    int x = y;
    y = d[j];
    int z = (j > 1 ? d[j - 1] : i);
    int del = (a[i-1] == b[j-1] ? 0 : 1);
    d[j] = min(x + del, y + 1, z + 1);
}
}
int temp = d[n];
return temp;
    }
}//C++

#include<iostream.h>
#include<string.h>
#include<fstream.h>
int Min(int a,int b,int c)
{int temp=(a<b?a:b);
return (temp<c?temp:c);
}int getEditDistance(char *a,char *b)
{int m=strlen(a);
int n=strlen(b);
int i,j,temp;
int **d;
d=new int *[m+1];
for(i=0;i<=m;i++)
{   d[i]=new int[n+1];
}d[0][0]=0;
for(i=1;i<=m;i++)
{   d[i][0]=i;
}for(j=1;j<=n;j++)
{   d[0][j]=j;
}for(i=1;i<=m;i++)
{   for(j=1;j<=n;j++)
   {
    int edit=(a[i-1]==b[j-1]?0:1);
    d[i][j]=Min((d[i-1][j-1]+edit),//更改或不变
(d[i][j-1]+1),//插入
(d[i-1][j]+1));//删除
   }
}temp=d[m][n];
delete d;
return temp;
}int main()
{ifstream infile("input.txt");
if(!infile)
{   cout<<"Can't open input.txt!"<<'\n';
   return 0;
}ofstream outfile("output.txt");
if(!outfile)
{   cout<<"Can't open output.txt!"<<'\n';
   return 0;
}char *a=new char[256];
char *b=new char[256];
infile>>a;
infile>>b;
int d=getEditDistance(a,b);
cout<<d;
cout<<endl;
outfile<<d;
infile.close();
outfile.close();
return 0;
}

1. Levenshtein Distance 来文史特距离 (单词距离)
学术界已经提出了不少字符串相似度计算方法,其中最常用、最简单的是编辑距离(Edit Distance),又称作来文史特距离(Levenshtein Distance)[5]。令字符串P=p1p2…pn 和W=w1w2…wm 是有穷字母表Σ上的两个字符串,ε表示空字符串。编辑操作是一个二元组(a,b),也可表示为a→b,其中a,b∈Σ∪{ε},(a,b)≠(ε,ε),a≠b。令W 由P 通过编辑操作a→b 生成,P=xay,W=xby,x 和y 为字符串。若a≠ε,b≠ε,则a→b 称为替换操作;若a=ε,b≠ε,则a→b 称为插入操作;若a≠ε,b=ε,则a→b 称为删除操作。字符串P 和W 的编辑距离ed(P,W)是由P 变换到W 所需要的最少编辑操作数目[6]。计算两个字符串编辑距离的标准算法是Wagner 和Fisher 提出的动态规划算法。令P和W 的长度分别为n 和m,计算P 和W 的编辑距离ed(P,W)的过程就是给一个n 行、m 列的矩阵edit 赋值的过程。edit 矩阵赋值过程如下:
edit(0,0)=0
edit(i,0)=i
edit(0,j)=j
edit(i,j)=min( edit(i-1,j)+1, edit(i,j-1)+1, edit(i-1,j-1)+ed(Pi,Wj) )
其中Pi 和Wj 分别表示P 的第i 个字符和W 的第j 个字符。若Pi=Wj,则ed(Pi,Wj)=0,否则ed(Pi,Wj)=1。该算法的时间复杂度和空间复杂度均为O(n×m),
算法执行结束后edit 矩阵第n 行第m 列元素值即为P 与W 的编辑距离。

2. 基于N-gram 计算两个字符串的相似度

字符串P 的N-gram 是P 中任意长度为N 的子串。例如,单词waist 的Bigram 有wa、ai、is 和st 四个。对于给定的字符串P 和W,其N-gram 相似度gram-count(P,W) 定义为同时在P 和W 中出现的N-gram 数目,即
( , ) | | P W gram− count P W = G IG 公式(1)其中GX 表示字符串X 的所有N-gram 构成的集合。例如,当P=waist,W=wait,N=2 时,P和W 共有的Bigram 有wa 和ai,因此gram-coun(P, W))=2。由于GP ∩GW 可以通过对N-gram的倒排索引链求交得到,因此gram-count(A,B)的计算速度很快,一般用于产生候选匹配结果。

对于大规模词典近似匹配算法,可以采用N-gram 相似度作为粗匹配度量指标,快速从背景词典中选出一定数量的候选匹配结果集C,对C 的每一个单词W,计算W与输入模式P的编辑距离ed(P,W) ,若ed(P,W)小于预先设定的编辑距离阈值k,W 则为模式P 的最终近似匹配结果。基于编辑距离的词典近似匹配问题可以形式化描述如下[5]:给定用户输入模式P,大规模背景词典D 和一个较小的自然数k,计算D 的子集R,使得R 中每一个单词W 与P 的编辑距离ed(P,W)不超过k。

最简单的计算模式P 的近似匹配的方法就是对背景词典的每一个单词W,计算P 与W 的编辑距离ed(P,W),从而确定P 的所有近似匹配结果。虽然有很多人对Wagner 和Fisher 提出的动态规划算法进行了改进,但由于背景词典一般都比较大,即便采用目前最好的计算编辑距离的算法,计算背景词典中每一个单词W 与P 的编辑距离,从而确定P 的近似匹配结果集也是不现实的。

为了解决这个问题,不少人采用了两阶段匹配策略,将词典近似匹配过程分为两个阶段:粗匹配阶段和细匹配阶段[8]。在粗匹配阶段通过一定策略过滤掉大部分不可能为匹配结果的词典单词,使背景词典中只有少部分单词成为候选匹配结果;细匹配阶段对第一阶段产生的候选匹配结果集中的每一个单词W,计算W 与用户输入模式P 的编辑距离,从而确定最终的词典近似匹配结果集合R。由于细匹配阶段中速度较慢,因此两阶段匹配策略的速度很大程度上
取决于粗匹配阶段产生的候选匹配结果数量。N-gram 索引是一种最常用的粗匹配词典索引结构[1]。N-gram 索引是一个倒排索引,索引项为词典单词中所有连续N 个字符的集合,索引项g 的倒排链是词典中所有包含g 的单词
的ID 集合。对于输入模式P,只有P 中出现的N-gram 对应倒排链中的单词才有可能成为最终匹配结果,因此细匹配阶段只需要对这些单词分别计算与P 的编辑距离,即可得到最终的近似匹配结果集合。一般来说,采用N-gram 索引后粗匹配阶段产生的候选匹配结果数量远远小于词典单词的总数,因此N-gram 索引可以提高词典近似匹配的速度。

总之,将N-Gram和单词距离结合使用达到快速单词纠错的能力。用N-Gram方法从词典中预选出大概满足要求的单词集合,然后用单词距离算出目标单词和集合中每个单词的距离,按单词距离排序即可。

来源:http://andynjux.blogbus.com/logs/48219445.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值