1. 程式人生 > >兩個字串的編輯距離-動態規劃方法

兩個字串的編輯距離-動態規劃方法

概念

字串的編輯距離,又稱為Levenshtein距離,由俄羅斯的數學家Vladimir Levenshtein在1965年提出。是指利用字元操作,把字串A轉換成字串B所需要的最少運算元。其中,字元操作包括:

  • 刪除一個字元     a) Insert a character
  • 插入一個字元     b) Delete a character
  • 修改一個字元     c) Replace a character

例如對於字串"if"和"iff",可以通過插入一個'f'或者刪除一個'f'來達到目的。

  一般來說,兩個字串的編輯距離越小,則它們越相似。如果兩個字串相等,則它們的編輯距離(為了方便,本文後續出現的“距離”,如果沒有特別說明,則預設為“編輯距離”)為0(不需要任何操作)。不難分析出,兩個字串的編輯距離肯定不超過它們的最大長度(可以通過先把短串的每一位都修改成長串對應位置的字元,然後插入長串中的剩下字元)。


問題描述

給定兩個字串A和B,求字串A至少經過多少步字元操作變成字串B。


問題分析

1)首先考慮A串的第一個字元

  假設存在兩個字串A和B,他們的長度分別是lenA和lenB。首先考慮第一個字元,由於他們是一樣的,所以只需要計算A[2...lenA]和B[2...lenB]之間的距離即可。那麼如果兩個字串的第一個字元不一樣怎麼辦?可以考慮把第一個字元變成一樣的(這裡假設從A串變成B串):

  • 修改A串的第一個字元成B串的第一個字元,之後僅需要計算A[2...lenA]和B[2...lenB]的距離即可;
  • 刪除A串的第一個字元,之後僅需要計算A[2...lenA]和B[1...lenB]的距離即可;
  • 把B串的第一個字元插入到A串的第一個字元之前,之後僅需要計算A[1...lenA]和B[2...lenB]的距離即可。

2)接下來考慮A串的第i個字元和B串的第j個字元。

  我們這個時候不考慮A的前i-1字元和B串的第j-1個字元。如果A串的第i個字元和B串的第j個字元相等,即A[i]=B[j],則只需要計算A[i...lenA]和B[j...lenB]之間的距離即可。如果不想等,則:

  • 修改A串的第i個字元成B串的第j個字元,之後僅需要計算A[i+1...lenA]和B[j+1...lenB]的距離即可;
  • 刪除A串的第i個字元,之後僅需要計算A[i+1...lenA]和B[j...lenB]的距離即可;
  • 把B串的第j個字元插入到A串的第i個字元之前,之後僅需要計算A[i...lenA]和B[j+1...lenB]的距離即可。

  寫到這裡,自然會想到用遞迴求解或者動態規劃求解,由於用遞迴會產生很多重複解,所以用動態規劃。

建動態規劃方程

  用edit[i][j]表示A串和B串的編輯距離。edit[i][j]表示A串從第0個字元開始到第i個字元和B串從第0個字元開始到第j個字元,這兩個字串的編輯距離。字串的下標從1開始。

  dis[0][0]表示word1和word2都為空的時候,此時他們的Edit Distance為0。很明顯可以得出的,dis[0][j]就是word1為空,word2長度為j的情況,此時他們的Edit Distance為j,也就是從空,新增j個字元轉換成word2的最小Edit Distance為j;同理dis[i][0]就是,word1長度為i,word2為空時,word1需要刪除i個字元才能轉換成空,所以轉換成word2的最小Edit Distance為i。

  則從上面的分析,不難推匯出動態規劃方程:

,其中

上式中的min()函式中的三個部分,對應三種字元操作方式:

edit[i-1][j]+1相當於給word2的最後插入了word1的最後的字元,插入操作使得edit+1,之後計算edit[i-1][j];

edit[i][j-1]+1相當於將word2的最後字元刪除,刪除操作edit+1,之後計算edit[i][j-1];

edit[i-1][j-1]+flag相當於通過將word2的最後一個字元替換為word1的最後一個字元。flag標記替換的有效次數。


演算法分析: 

  也就是說,就是將一個字串變成另外一個字串所用的最少運算元,每次只能增加、刪除或者替換一個字元。
  首先我們令word1和word2分別為:michaelab和michaelxy(為了理解簡單,我們假設word1和word2字元長度是一樣的),dis[i][j]作為word1和word2之間的Edit Distance,我們要做的就是求出michaelx到michaely的最小steps。

  首先解釋下dis[i][j]:它是指word1[i]和word2[j]的Edit Distance。dis[0][0]表示word1和word2都為空的時候,此時他們的Edit Distance為0。很明顯可以得出的,dis[0][j]就是word1為空,word2長度為j的情況,此時他們的Edit Distance為j,也就是從空,新增j個字元轉換成word2的最小Edit Distance為j;同理dis[i][0]就是,word1長度為i,word2為空時,word1需要刪除i個字元才能轉換成空,所以轉換成word2的最小Edit Distance為i。下面及時初始化程式碼:

for(int i =0; i < row; i++) dis[i][0]= i;
for(int j =0; j < col; j++) dis[0][j]= j;

    下面來分析下題目規定的三個操作:新增,刪除,替換。
    假設word1[i]和word2[j](此處i = j)分別為:michaelab和michaelxy
    如果b==y, 
        那麼:dis[i][j] = dis[i-1][j-1]。                                                              
    如果b!=y,
        那麼:新增:也就是在michaelab後面新增一個y,那麼word1就變成了michaelaby,
             此時  dis[i][j] = 1 + dis[i][j-1];
    上式中,1代表剛剛的新增操作,新增操作後,word1變成michaelaby,word2為michaelxy。
    dis[i][j-1]代表從word1[i]轉換成word2[j-1]的最小Edit Distance,也就是michaelab轉換成michaelx的最小
    Edit Distance,由於兩個字串尾部的y==y,所以只需要將michaelab變成michaelx就可以了,而他們之間的最
    小Edit Distance就是dis[i][j-1]。
    刪除:也就是將michaelab後面的b刪除,那麼word1就變成了michaela,此時dis[i][j] = 1 + dis[i-1][j];
    上式中,1代表剛剛的刪除操作,刪除操作後,word1變成michaela,word2為michaelxy。dis[i-1][j]代表從
    word[i-1]轉換成word[j]的最小Edit Distance,也就是michaela轉換成michaelxy的最小Edit Distance,所以
    只需要將michaela變成michaelxy就可以了,而他們之間的最小Edit Distance就是dis[i-1][j]。
    替換:也就是將michaelab後面的b替換成y,那麼word1就變成了michaelay,此時dis[i][j] = 1 + dis[i-1][j-1];
    上式中,1代表剛剛的替換操作,替換操作後,word1變成michaelay,word2為michaelxy。dis[i-1][j-1]代表從
    word[i-1]轉換成word[j-1]的最小Edit Distance,也即是michaelay轉換成michaelxy的最小Edit Distance,由
    於兩個字串尾部的y==y,所以只需要將michaela變成michaelx就可以了,而他們之間的最小Edit Distance就是
    dis[i-1][j-1]。
舉例:
比如要計算cafe和coffee的編輯距離。cafe→caffe→coffe→coffee先建立一個6×8的表(cafe長度為4,coffee長度為6,各加2)(1):
coffee
c
a
f
e1
接著,在如下位置填入數字(表2):
coffee
0123456
c1
a2
f3
e42
從3,3格開始,開始計算。取以下三個值的最小值:
  • 如果最上方的字元等於最左方的字元,則為左上方的數字。否則為左上方的數字+1。(對於3,3來說為0)
  • 左方數字+1(對於3,3格來說為2)
  • 上方數字+1(對於3,3格來說為2)
因此為格3,3為0(表3)
coffee
0123456
c1  0 
a2
f3
e43     
迴圈操作,推出下表
coffee
0123456
c10123   4   5   
a2112345
f3221234
e4332223
取右下角,得編輯距離為3
程式碼:
<pre name="code" class="cpp">#include<stdio.h>
#include<string.h>
char s1[1000],s2[1000];
int min(int a,int b,int c)
{
    int tmp=a<b?a:b;
    return tmp<c?tmp:c;
}
void editDistance(int len1,int len2)
{
    int **d=new int*[len1+1];
    for(int i=0;i<=len1;i++)
        d[i]=new int[len2+1];
    int i,j;
    for(i=0;i<=len1;i++)
        d[i][0]=i;
    for(j=0;j<=len2;j++)
        d[0][j]=j;
    for(i=1;i<=len1;i++)
    {
        for(j=1;j<=len2;j++)
        {
            int cost=s1[i]==s2[j]?0:1;
            int deletion=d[i-1][j]+1;
            int insertion=d[i][j-1]+1;
            int substitution=d[i-1][j-1]+cost;
            d[i][j]=min(deletion,insertion,substitution);
        }
    }
    printf("距離為:%d\n",d[len1][len2]);
    for(int i=0;i<=len1;i++)
    {
        delete[] d[i];
    }
    delete[] d;
}
 
int main()
{
    while(scanf("%s%s",s1,s2)!=EOF)
    {
        editDistance(strlen(s1),strlen(s2));
    }
}


相關推薦

字串編輯距離-動態規劃方法

概念 字串的編輯距離,又稱為Levenshtein距離,由俄羅斯的數學家Vladimir Levenshtein在1965年提出。是指利用字元操作,把字串A轉換成字串B所需要的最少運算元。其中,字元操作包括: 刪除一個字元     a) Insert a charac

最長公共子序列與編輯距離動態規劃原理分析

前段時間看過最長公共子序列的動態規劃演算法,這兩天又看到了編輯距離的動態規劃演算法,感覺兩者有很相似的地方,而狀態轉移方程又不十分直觀,所以打算把其原理記錄下來,以防以後忘記。 先看最長公共子序列,記兩個序列分別為a[m],b[n].其狀態轉移方程如下: 從中我們可以看出

給定字串,請設計一個方法來判定其中一個字串是否為另一個字串的置換(容易)

在vs2013寫的程式,使用sort進行排序,然後對比是否完全一樣即可/* 給定兩個字串,請設計一個方法來判定其中一個字串是否為另一個字串的置換。 置換的意思是,通過改變順序可以使得兩個字串相等。 */ #include "stdafx.h" #include <i

TZOJ 1072: 編輯距離(動態規劃)

1072: 編輯距離 時間限制(普通/Java):1000MS/10000MS     記憶體限制:65536KByte 總提交: 917            測試通過:275 描述 假設字

java中字串連線的三種方法

java中兩個字串連線有以下三種方法: 第一種方法:使用+; 第二種方法:使用concat(); 第三種方法:使用append(); 如下程式碼: public class Practice { //第一種方法:使用+ public static String

編輯距離 演算法詳述計算字串差異 c++程式碼

編輯距離即從一個字串變換到另一個字串所需要的最少變化操作步驟(以字元為單位,如son到sun,s不用變,將o->s,n不用變,故操作步驟為1)。 為了得到編輯距離,我們畫一張二維表來理解,以beauty和batyu為例: 圖示如1單元格位置即是兩個單詞的第一個字元[b]比較得到的值,其值由它上方的

動態規劃字串編輯距離(Levenshtein距離)演算法

基本介紹 Levenshtein距離是一種計算兩個字串間的差異程度的字串度量(string metric)。我們可以認為Levenshtein距離就是從一個字串修改到另一個字串時,其中編輯單個字元(比如修改、插入、刪除)所需要的最少次數。俄羅斯科學家Vladi

字串最長公共子串(動態規劃

code如下: //Longest common sequence, dynamic programming method void FindLCS(char *str1, char *str2) { if(str1 == NULL || str2 == NULL)

C++實現字串之間的Levenshtein Distance(編輯距離)

1.什麼是Levenshtein Distance Levenshtein Distance,又稱編輯距離,指的是兩個字串之間,由一個轉換成另一個所需的最少編輯操作次數。許可的編輯操作包括將一個字元替

字串之間的最短編輯距離

1.演算法原理編輯距離(Edit Distance)是指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數,編輯操作包括增、刪、改操作。例如將kitten一字轉成sitting:sitten (k→s)sittin (e→i)sitting (→g),最短編輯距離為3.跟“

動態規劃求解字串的最大公共子串問題

最大公共子串長度問題就是:求兩個串的所有子串中能夠匹配上的最大長度是多少。比如:"abcdkkk" 和 "baabcdadabc",可以找到的最長的公共子串是"abcd",所以最大公共子串長度為4。下面的程式是採用矩陣法進行求解的,這對串的規模不大的情況還是比較有效的解法。請

判斷字串不同的json是否等價(附:將等價但是不同json調整成同一字串方法

在做軟體和網路測試的時候,經常需要對排版格式與內部結構順序不固定的json物件進行分析對比,而json基本語法下,同樣的一個json物件,字串變化可以千變萬化,json內各個層欄位順序調轉,排版變化,打亂順序的json陣列+多層巢狀,等等各種因素,都會造成對比上的困難。 以下由淺及深談談幾種

5.12 陣列中字串的最小距離

【題目】:   給定一個字串陣列strs,再給定兩個字串str1和str2,返回在strs中str1和str2的最小距離,如果str1或str2為null,或不在strs中,返回-1   舉例:     strs=["1", "3", "3", "3", "2", "3", "1"],str1="1",

java山科實驗4-4 設計一個類的方法,其輸入是字串的集合,打印出這集合的笛卡爾乘積。

設計一個類的方法,其輸入是兩個字串的集合(每個集合中的字串有相同的意義,例如,一個全是姓名的字串,一個全是班級的字串),打印出這兩個集合的笛卡爾乘積。 package zuoye4; public

求陣列中字串的最小距離 Python 版

題目: 給定一個數組 strs,其中的資料都是字串,給定兩個字串 str1,str2。如果這兩個字串都在 strs陣列中,就返回它們之間的最小距離;如果其中任何一個不在裡面,則返回 -1;如果兩個字串相等,則返回 0。 例如:給定[‘*’,’3’,’*’,’

陣列分割, 把陣列分解成和相等的部分--動態規劃方法

#include "stdafx.h" #include "stdlib.h" #include <stack> using namespace std; bool isSubsetSplit(int A[], int len, int sum, stack&

php實現比較字串日期大小的方法

<?php function dateBDate($date1, $date2) { // 日期1是否大於日期2 $month1 = date("m", strtotime($

Java中打印出來完全相同的字串,用equals方法比較返回的卻是false的原因

今天遇到一個奇怪的問題,兩個字串打印出來完全一樣,可是用equals比較就是返回false,單獨寫了兩個字串列印比較也是true,到底什麼原因呢。 原來一個是通過ResourceBundle讀取資原始

求二叉樹中任意結點的距離

case itl wid get ren return roo [] fall 求二叉樹中任意兩個結點的距離實現步驟:計算跟到第一個結點的距離;計算跟到第二個結點的距離;計算lca;計算跟到lca結點的距離;結果為(1) + (2) - 2 * (4),因為重復計算了兩次的

(6name="hobby"的復選項,按鈕)來區分三種方法的不同---區別getElementByID,getElementsByName,getElem

-- cnblogs checkbox javascrip int ava mage clear img <form> 請選擇你愛好:<br> <input type="checkbox" name="hob