- > 動規講解基礎講解六——編輯距離問題


給定兩個字符串S和T,對於T我們允許三種操作:


(1) 在任意位置添加任意字符
(2) 刪除存在的任意字符
(3) 修改任意字符 

問最少操作多少次可以把字符串T變成S? 

例如: S=  “ABCF”   T = “DBFG”

那么我們可以

(1) 把D改為A
(2) 刪掉G
(3) 加入C

所以答案是3。
 
分析: 這個最少的操作次數,通常被稱之為編輯距離。“編輯距離”一次本身具有最短的意思在里面。因為題目有“最短”這樣的關鍵詞,首先我們想到的是BFS。是的,當S的距離為m, T的距離為n的時候,我們可以找到這樣的操作次數的界限:

(1) 把T中字符全刪了,再添加S的全部字符,操作次數m + n。
(2) 把T中字符刪或加成m個,再修改 操作次數最多 |n – m| + m。

雖然,我們找到了這樣的上界,BFS從實際角度並不可行,因為搜索空間是指數的,這取決於S中的字符種類——具體的數量級不好估計。
這個問題之所以難,是難在有“添加”“刪除”這樣的操作,很麻煩。我們試試換個角度理解問題,把它看成字符串對齊的問題,事實上從生物信息學對比基因的角度,我們可以這樣理解問題。

給定字符串S和T,我們可以用一種特殊字符促成兩個字符串的對齊。我們加的特殊字符是“-”, 我們允許在S和T中任意添加這種特殊字符使得它長度相同,然后讓這兩個串“對齊”,最終兩個串相同位置出現了不同字符,就扣1分,我們要使得這兩個串對齊扣分盡量少。

對於例子 我們實際上采取了這樣的對齊方式:

12345
ABCF-
DB-FG

注意:如果要對齊,兩個“-”相對是沒有意義的,所以我們要求不出現這種情況。
那么看一下:
(1) S,T對應位置都是普通字符,相同,則不扣分。 例如位置2,4
(2) S,T對應位置都是普通字符,不同,則扣1分。 例如位置1
(3) S在該位置是特殊字符,T在該位置是普通字符,則扣1分,例如位置5
(4) S在該位置是普通字符,T在該位置是特殊字符,則扣1分,例如位置3

我們來看看扣分項目對應什么?

(1) 不扣分,直接對應
(2) 對應把T中對應位置的字符修改
(3) 對應在T中刪除該字符
(4) 對應在T中添加該字符

好了,目標明確,感覺像不像 LCS?我們嘗試一下:
設f(i,j)表示S的前i位和T的前j位對齊后的最少扣分。

那我們來看看最后一位,對齊的情況

(1) 必須S[i] == T[j], 這時前i – 1和j – 1位都已經對齊了,這部分肯定要最少扣分。這種情況下最少的扣分是f(i-1,j-1)
(2) 和(1)類似,S[i]≠T[j],這種情況下最少的扣分是f(i -1, j – 1) + 1
(3) S的前i位和T的前(j – 1)位已經對齊了,這部分扣分也要最少。這種情況下最少的扣分是f(i,j-1) + 1
(4) S的前(i-1)位已經和T的前j位對齊了,這部分扣分要最少。這種情況下最少的扣分是f(i,j-1) + 1

具體f(i,j)取什么值,顯然是要看哪種情況的扣分最少。
為了方便,我們定義函數same(i,j)表示如果S[i] == T[j]則為0,否則為1。

我們來表示一下遞推式:

f(i,j) = min(f(i – 1, j – 1) + same(i,j), f(i – 1,j ) + 1, f(i, j – 1) + 1)

初值是什么?

f(0, j) = j
f(i, 0) = i

這時因為對於S的前0位,我們只能在之前加入“-”,或者說把T全部刪掉了。類似地,對於T地前0位,我們只能把S的字符都加進來,別無選擇。
注意上述兩個式子的重合點 f(0,0) = 0也符合我們的定義,並不矛盾。

時間復雜度? O(m * n),空間復雜度? O(m * n)。同樣我們發現到f(i,j)只與本行和上一行有關,可以省掉一維的空間復雜度,從而達到O(n)。
優化后的偽代碼:
for j = 0 to n do
    f[j] = j
endfor

for i = 1 to m do
    last = f[0]
    f[0] = i
    for j = 1 to n do 
        temp = f[i,j]
        f[i,j] = min(last + same(i,j), temp + 1, f[j – 1] + 1)
        last = temp
    endfor
endfor

注意: 我們對於i實際上更新j的順序是由小到達的,所以我們需要保存“舊的”f[i-1,j – 1]。

題解:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char a[1010],b[1010];
int f[1010][1010];
int main()
{
    cin>>a;
    cin>>b;
    int m=strlen(a);
    int n=strlen(b);
    for(int i=1;i<=m;i++)    f[i][0]=i;
    for(int j=1;j<=n;j++)    f[0][j]=j;
    for(int i=1;i<=m;i++)
      for(int j=1;j<=n;j++)
          if(a[i-1]==b[j-1])    f[i][j]=f[i-1][j-1];
          else f[i][j]=min(min(f[i-1][j-1],f[i-1][j]),f[i][j-1])+1;
    cout<<f[m][n];    
} 

   如果對你有所幫助,別忘了加好評哦;么么噠!!下次見!88


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM