最短編輯距離

1.最短編輯距離的介紹

①基本定義

      所謂編輯距離(Edit Distance),是指兩個字符串之間,由一個轉成另一個所需的最少編輯操作次數。許可的編輯操作總共有三個:將一個字符替換成另一個字符、插入一個字符或者刪除一個字符。講道理的話,編輯距離越小,兩個字符串就越相似。對了,它又叫作Levenshtein距離,因爲這個概念是在1965年由俄羅斯科學家Vladimir Levenshtein提出的。

②舉例說明

    最短編輯距離這個編程概念有些複雜,接下來我將爲大家舉幾個例說明:

I.將一個字符替換成另一個字符

例如:

tall--->ball

she--->shy

luck--->lock

II.插入一個字符

比如:

fed--->feed

to--->two

at--->ant

III.刪除一個字符

譬如:

open--->pen

changed--->change

down--->own

IV.綜合

綜上所述,可以轉化兩個差別較大的字符串,就像這樣:

java--->pava--->pasa--->pasca--->pascal

如果現在大家還有什麼不懂的,請在評論處說明,我一定第一時間解答。

2.最短編輯距離的公式

①公式總結

總的說來,有四種情況:

I.刪除

a[i-1][j]+1;

II.插入

a[i][j-1]+1;

III.替換

a[i][j]=a[i-1][j-1]+1;

IV.不變(相等時)

a[i][j]=a[i-1][j-1];

②代碼實現

聲明:本人只會C/C++,其它的是從網上搜集的。

I*C++(稍微改改就成了C語言)

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
char a[1001],b[1001];
int lena,lenb,f[1001][1001];
int min(int x,int y,int z)
{
	if(x<=y&&x<=z) return x;
	else if(y<=x&&y<=z) return y;
	else return z;
}
main()
{
	scanf("%s%s",a,b);
	lena=strlen(a);
	lenb=strlen(b);
	for(int i=0;i<=lena;i++)
		for(int j=0;j<=lenb;j++)
		{
			if(!i)  
			{  
				f[i][j]=j;  
				continue;  
			}  
			if(!j)  
			{  
				f[i][j]=i;  
				continue;  
			}  
			if(a[i-1]==b[j-1])
				f[i][j]=f[i-1][j-1];
			else
				f[i][j]=1+min(f[i-1][j-1],f[i][j-1],f[i-1][j]);
		}
	cout<<f[lena][lenb]<<endl;
}

II*pascal

var
st1,st2:string;
d:array[0..1000000] of integer;
i,j,m,n,cost:integer;
begin
m:=length(st1);
n:=length(st2);
for i:=0 to m do
d[i,0]:=i;
for j:= 0 to n do
d[0,j]:=j;
for i:= 1 to m do
for j:= 1 to n do
begin
if st1[i]=st2[j] then
cost:=0
else cost:=1;
if cost=0 then
begin
if (d[i-1,j]<d[i,j-1]) and (d[i-1,j]+1<d[i-1,j-1]+cost)
then d[i,j]:=d[i-1,j]+1
else if d[i,j-1]+1< d[i-1,j-1]+cost
then d[i,j]:=d[i,j-1]+1
else d[i,j]:=d[i-1,j-1]+cost;
end;
end;

III*Java

publicclassStringSimilar{
 
 
 //編輯距離求串相似度
 
 
publicdoublegetStringSimilar(Strings1,Strings2){
 
 
//TODOAuto-generatedmethodstub
 
 
doubled[][];//matrix
 
 intn;//lengthofs
 
 intm;//lengthoft
 
 inti;//iteratesthroughs
 
 intj;//iteratesthrought
 
 chars_i;//ithcharacterofs
 
 chart_j;//jthcharacteroft
 
 doublecost;//cost 
//Step1
 n=s1.length();
 m=s2.length();
 if(n==0){
 returnm;
 }
 if(m==0){
 returnn;
 }
 d=newdouble[n+1][m+1];
 //Step2
 for(i=0;i<=n;i++){
 d[i][0]=i;
 }
 for(j=0;j<=m;j++){
 d[0][j]=j;
 }
 //Step3
 for(i=1;i<=n;i++){
 s_i=s1.charAt(i-1);
 //Step4
 for(j=1;j<=m;j++){
 t_j=s2.charAt(j-1);
 //Step5
 if(s_i==t_j){
 cost=0;
 }else{
 cost=1;
 }
 //Step6
 d[i][j]=Minimum(d[i-1][j]+1,d[i][j-1]+1, d[i-1][j-1]+cost);
 }
 }
 //Step7
 returnd[n][m];
 }
 //求最小值
 privatedoubleMinimum(doublea,doubleb,doublec){
 //TODOAuto-generatedmethodstub
 doublemi;
 mi=a;
 if(b<mi){
 mi=b;
 }
 if(c<mi){
 mi=c;
 }
 returnmi;
 }
 } 

IV*R語言

leven <- function(stra,strb)
{
    lena=nchar(stra)
    lenb=nchar(strb)
    tb=array(0,c(lena+1,lenb+1))
    tb[1,]=0:lenb
    tb[,1]=0:lena
    for(i in 1:lena)
    {
        for(j in 1:lenb)
        {
            row=i+1
            col=j+1
            k=1
            if(substr(stra,i,i)==substr(strb,j,j))k=0
            step=min(tb[i,j]+k,tb[i+1,j]+1,tb[i,j+1]+1)
            tb[i+1,j+1]=step
        }
    }
    return(tb[lena+1,lenb+1])
}

V*Scala

import Math.min
 
def distance(s0: String, s1: String): Int = {
  if (s0.isEmpty) return s1.length
  if (s1.isEmpty) return s0.length
  val dis = Array.ofDim[Int](s0.length, s1.length)
  for (i <- 0 until s0.length; j <- 0 until s1.length)
    dis(i)(j) = (i, j) match {
      case (0, j) => j
      case (i, 0) => i
      case _ =>
        if (s0(i) == s1(j))
          dis(i - 1)(j - 1)
        else
          min(min(dis(i - 1)(j) + 1, dis(i)(j - 1) + 1), dis(i - 1)(j - 1) + 1)
    }
 
  return dis(s0.length - 1)(s1.length - 1)
}

VI*VB

Function EditDistance(Str1 As String,Str2 As String)As Long
     
Dim Distances() As Long '用來儲存數據
 
Dim Len1 As Long, Len2 As Long '存儲字符串長度
     
Len1 = Len(Str1): Len2 = Len(Str2)
 
If Len1 = 0 Then'當第一個字符串長度爲0
         
EditDistance=Len2'返回Len2
 
ElseIf Len2 = 0 Then'第二個字符串長度爲0
         
EditDistance = Len1'返回Len1
 
Else'長度均不爲0
         
Redim Distances(0ToLen1, OToLen2)'二維數組重定義
 
Dim i As Long, j As Long, Nums(1 To 3) As Long'Nums用來存儲三個值
         
Dim MinNum As Long'用來存儲Nums中最大值
 
'初始化數據
         
For i = 0 To Len1
 
Distance(i, 0) = i
         
Next i 
 
For i = 0 To Len2 
             
Distance(0, i)=i
 
Next i
 
'正式計算
         
For i = 1 To Len1
 
For j = 1 To Len2
                  
Nums(1) = Distances(i - 1, j) + 1
 
Nums(2) = Distances(i, j - 1) + 1
                  
If Mid(Str1, i,1 ) = Mid(Str2, j, 1) Then'如果Str1第i個字符和Str2第j個字符相等
 
Nums(3) = Distance(i - 1, j - 1)
                  
Else'如果不相等
 
Nums( 3 ) = Distance(i - 1,j - 1) + 1 
                  
End If
 
MinNum = Nums(1)
                  
If MinNum > Nums(2) Then MinNum = Nums(2)
 
If MinNum > Nums(3) Then MinNum = Nums(3)
                  
Distances(i, j) = MinNum
 
Next j
         
Nexti
 
'返回右下角值
 
EditDistance = Distances(Len1, Len2)
     
End If 
 
End Function

VII*C#

public class StringComparator
    {
        public static int LevenshteinDistance(string source, string target)
        {
            int columnSize = source.Length;
            int rowSize = target.Length;
            if (columnSize == 0)
            {
                return rowSize;
            }
            if (rowSize == 0)
            {
                return columnSize;
            }

            int[,] matrix = new int[rowSize + 1, columnSize + 1];
            for (int i = 0; i <= columnSize; i++)
            {
                matrix[0, i] = i;
            }
            for (int j = 1; j <= rowSize; j++)
            {
                matrix[j, 0] = j;
            }

            for (int i = 0; i < rowSize; i++)
            {
                for (int j = 0; j < columnSize; j++)
                {
                    int sign;
                    if (source[j].Equals(target[i]))
                        sign= 0;
                    else
                        sign = 1;
                    matrix[i + 1, j + 1] = Math.Min(Math.Min(matrix[i, j] +  sign, matrix[i + 1, j]), matrix[i, j + 1] + 1);
                }
            }

            return matrix[rowSize, columnSize];
        }

        public static float StringSimilarity(string source, string target)
        {
            int distance = LevenshteinDistance(source, target);
            float maxLength = Math.Max(source.Length, target.Length);

            return (maxLength - distance) / maxLength;
        }
    }

VIII*SQL

CREATE FUNCTION edit_distance(@s1 nvarchar(3999), @s2 nvarchar(3999))

RETURNS int
AS
BEGIN
DECLARE @s1_len int, @s2_len int
DECLARE @i int, @j int, @s1_char nchar, @c int, @c_temp int
DECLARE @cv0 varbinary(8000), @cv1 varbinary(8000)

SELECT

@s1_len = LEN(@s1), @s2_len = LEN(@s2), @cv1 = 0x0000,

@j = 1, @i = 1, @c = 0

WHILE @j <= @s2_len

SELECT @cv1 = @cv1 + CAST(@j AS binary(2)), @j = @j + 1

WHILE @i <= @s1_len

BEGIN
SELECT
@s1_char = SUBSTRING(@s1, @i, 1), @c = @i,

@cv0 = CAST(@i AS binary(2)), @j = 1

WHILE @j <= @s2_len

BEGIN
SET @c = @c + 1
SET @c_temp = CAST(SUBSTRING(@cv1, @j+@j-1, 2) AS int) +
CASE WHEN @s1_char = SUBSTRING(@s2, @j, 1) THEN 0 ELSE 1 END
IF @c > @c_temp SET @c = @c_temp
SET @c_temp = CAST(SUBSTRING(@cv1, @j+@j+1, 2) AS int)+1
IF @c > @c_temp SET @c = @c_temp
SELECT @cv0 = @cv0 + CAST(@c AS binary(2)), @j = @j + 1

END

SELECT @cv1 = @cv0, @i = @i + 1

END

RETURN @c

END

3.最短編輯距離的應用

①DNA分析

基因學的一個主要主題就是比較 DNA 序列並嘗試找出兩個序列的公共部分。如果兩個 DNA 序列有類似的公共子序列,那麼這些兩個序列很可能是同源的。在比對兩個序列時,不僅要考慮完全匹配的字符,還要考慮一個序列中的空格或間隙(或者,相反地,要考慮另一個序列中的插入部分)和不匹配,這兩個方面都可能意味着突變(mutation)。在序列比對中,需要找到最優的比對(最優比對大致是指要將匹配的數量最大化,將空格和不匹配的數量最小化)。如果要更正式些,可以確定一個分數,爲匹配的字符添加分數、爲空格和不匹配的字符減去分數。
全局序列比對嘗試找到兩個完整的序列 S1和 S2之間的最佳比對。以下面兩個 DNA 序列爲例:
S1= GCCCTAGCG
S2= GCGCAATG
如果爲每個匹配字符一分,一個空格扣兩分,一個不匹配字符扣一分,那麼下面的比對就是全局最優比對:
S1'= GCCCTAGCG
S2'= GCGC-AATG
連字符(-)代表空格。在 S2'中有五個匹配字符,一個空格(或者反過來說,在 S1'中有一個插入項),有三個不匹配字符。這樣得到的分數是 (5 * 1) + (1 * -2) + (3 * -1) = 0,這是能夠實現的最佳結果。
使用局部序列比對,不必對兩個完整的序列進行比對,可以在每個序列中使用某些部分來獲得最大得分。使用同樣的序列 S1和 S2,以及同樣的得分方案,可以得到以下局部最優比對 S1''和 S2'':
S1 = GCCCTAGCG
S1''= GCG
S2''= GCG
S2 = GCGCAATG
這個局部比對的得分是 (3 * 1) + (0 * -2) + (0 * -1) = 3。

②拼寫糾錯(Spell Correction)

又稱拼寫檢查(Spell Checker),將每個詞與詞典中的詞條比較,英文單詞往往需要做詞幹提取等規範化處理,如果一個詞在詞典中不存在,就被認爲是一個錯誤,然後試圖提示N個最可能要輸入的詞——拼寫建議。常用的提示單詞的算法就是列出詞典中與原詞具有最小編輯距離的詞條。

這裏肯定有人有疑問:對每個不在詞典中的詞(假如長度爲len)都與詞典中的詞條計算最小編輯距離,時間複雜度是不是太高了?的確,所以一般需要加一些剪枝策略,如:

    因爲一般拼寫檢查應用只需要給出Top-N的糾正建議即可(N一般取10),那麼我們可以從詞典中按照長度依次爲len、len-1、len+1、len-2、len-3、...的詞條比較;
    限定拼寫建議詞條與當前詞條的最小編輯距離不能大於某個閾值;
    如果最小編輯距離爲1的候選詞條超過N後,終止處理;
    緩存常見的拼寫錯誤和建議,提高性能。

③命名實體抽取(Named Entity Extraction)

由於實體的命名往往沒有規律,如品牌名,且可能存在多種變形、拼寫形式,如“IBM”和“IBM Inc.”,這樣導致基於詞典完全匹配的命名實體識別方法召回率較低,爲此,我們可以使用編輯距離由完全匹配泛化到模糊匹配,先抽取實體名候選詞。

具體的,可以將候選文本串與詞典中的每個實體名進行編輯距離計算,當發現文本中的某一字符串的編輯距離值小於給定閾值時,將其作爲實體名候選詞;獲取實體名候選詞後,根據所處上下文使用啓發式規則或者分類的方法判定候選詞是否的確爲實體名。

④實體共指(Entity Coreference)

通過計算任意兩個實體名之間的最小編輯距離判定是否存在共指關係?如“IBM”和“IBM Inc.”, "Stanford President John Hennessy "和"Stanford University President John Hennessy"。

⑤機器翻譯(Machine Translation)

識別平行網頁對:由於平行網頁通常有相同或類似的界面結構,因此平行網頁在HTML結構上應該有很大近似度。首先將網頁的HTML標籤抽取出來,連接成一個字符串,然後用最小編輯距離考察兩個字符串的近似度。實際中,此策略一般與文檔長度比例、句對齊翻譯模型等方法結合使用,以識別最終的平行網頁對。
    自動評測:首先存儲機器翻譯原文和不同質量級別的多個參考譯文,評測時把自動翻譯的譯文對應到與其編輯距離最小的參考譯文上,間接估算自動譯文的質量。

⑥字符串核函數(String Kernel)

最小編輯距離作爲字符串之間的相似度計算函數,用作核函數,集成在SVM中使用。

發佈了89 篇原創文章 · 獲贊 120 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章