【51nod 教程】編輯距離問題(動態規劃)

給定兩個字符串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)。
優化後的僞代碼:
 
1
2
3
4
5
6
7
8
9
10
11
12
13
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 + 1f[j – 1] + 1)
        last = temp
    endfor
endfor
 

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

輸入

第1行:字符串a(a的長度 <= 1000)。
第2行:字符串b(b的長度 <= 1000)。

輸出

輸出a和b的編輯距離

輸入示例

kitten
sitting

輸出示例

3

代碼:


#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<stack>
using namespace std;
const int MAX=1000+10;

char str1[MAX],str2[MAX];
int dp[MAX][MAX];

//int minn(int a,int b,int c){//自己寫的比較函數,估計錯了,WA了好幾次
//	if(a<b && a<c)
//	return a;
//	else if(b<a && b<c)
//	return b;
//	return c;
//}

int main(){
	while(~scanf("%s %s",str1+1,str2+1)){
		memset(dp,0,sizeof(dp));
		str1[0]=str2[0]='0';
		int len1=strlen(str1)-1;
		int len2=strlen(str2)-1;
		for(int i=0;i<=len2+1;i++){
			dp[0][i]=i;
		}
		for(int i=0;i<=len1+1;i++)
		dp[i][0]=i;
		int temp;
		for(int i=1;i<=len1;i++){
			for(int j=1;j<=len2;j++){
				temp=min(dp[i-1][j]+1,dp[i][j-1]+1);//狀態轉移方程
				if(str1[i]==str2[j])
				dp[i][j]=min(temp,dp[i-1][j-1]);
				else
				dp[i][j]=min(temp,dp[i-1][j-1]+1);
			}
		}
		cout<<dp[len1][len2]<<endl;
	}
	return 0;
}











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章