題目:
對於一個字符串a可以通過增加一個字符、刪除一個字符、修改一個字符,將字符串a變成字符串b,例如
a= abcddefg
b = abcefg
可以通過a字符串刪除兩個dd得到b字符串,也可以通過b字符串增加dd編程a字符串,從上面的分析可以知道,增加和刪除的代價必須是相同的,這樣a字符串變成b字符串的代價和b字符串變成a字符串的代價纔會是相同的,否這可能產生代價不對稱的情況。其實我們可以設定修改和增加(刪除)的代價是不同的,當然也可以認爲他們是一樣的。
實際的計算過程可以如下進行:
1)比較a[i]和b[j];
2)如果a[i] == b[j],那麼distance = EditDistance(a[i + i], b[j + 1]) + 0;
3)如果a[i] != b[j],那麼可以經過如下操作使得a[i]等於b[j]
a) a[i]前增加b[j],那麼distance = EditDistance(a[i], b[j + 1] + insert_cost
b)b[j]前增加a[i],那麼distance = EditDistance(a[i + 1], b[j]) + insert_cost
c)刪除a[i],那麼distance = EditDistance(a[i + 1], b[j]) + delete_cost
d)刪除b[j],那麼distance = EditDistance(a[i], b[j + 1] + delete_cost
e)a[i]變成b[j],那麼distance = EditDistance(a[i + 1], b[j + 1] + replace_cost
f)b[j]變成a[i],那麼distance = EditDistance(a[i + 1], b[j + 1] + replace_cost
如果insert_cost == delete_cost,那麼a添加字符變成b和b刪除字符變成a是等價的,a[i]變成b[j]與b[j]變成a[i]也是等價的,因此實際需要考慮的代價就是下面3種情況:
i) distance = EditDistance(a[i], b[j + 1] + insert_cost(或delete_cost)
ii) distance = EditDistance(a[i + 1], b[j] + insert_cost(或delete_cost)
iii)distance = EditDistance(a[i + 1], b[j + 1] + replace_cost
如果我們回顧一下最長公共子序列問題(LCS),就會發現這個問題和LCS問題幾乎是等價的。因爲可以這樣理解,找出a和b的LCS,保持LCS對齊不變,增加刪除一些字符就完成了變換,而這樣的代價應該是最小的(猜測的,沒有證明)
這樣一個問題,我們可以使用遞歸來解決。
當然的解決方案是用動態規劃的方法解決,採用動態規劃解決時,我們假設前面字符串都已經變換相同了,那麼在a[i]變成b[j]的過程中需要對比如下代價:
1)如果a[i] == b[j],那麼前面的狀態可能是a[i - 1] b[j - 1]或者 a[i - 1] b[j]或者a[i] b[j - 1],我們要比較這些可能的轉換過程中哪個代價更小;
2)如果a[i] != b[j],那麼:
i)a[i] b[j]可能從a[i - 1] b[j - 1]狀態通過replace a[i] to a[j] + replace的代價來實現;
ii)a[i] b[j]可能從a[i ] b[j - 1]狀態通過爲a[i]前面添加一個b[j -1] + insert的代價來實現;
iii)a[i] b[j]可能從a[i - 1] b[j]狀態通過刪除a[i - 1] + delete的代價來實現;
而這些代價就通過一個向量cost向量來存儲。
程序代碼如下:
#include <stdio.h>
#include <string>
int Min(int a, int b, int c) {
int tmp = a > b ? b : a;
tmp = tmp > c ? c : tmp;
return tmp;
}
int EditDistance(const std::string& a, int a_offset, const std::string& b, int b_offset) {
if (a_offset == a.size() && b_offset < b.size()) {
return EditDistance(a, a_offset, b, b_offset + 1) + 1 ;
} else if (b_offset == b.size() && a_offset < a.size()) {
return EditDistance(a, a_offset + 1, b, b_offset) + 1;
} else if (a_offset == a.size() && b_offset == b.size()) {
return 0;
} else {
if (a[a_offset] == b[b_offset]) {
return EditDistance(a, a_offset + 1, b, b_offset + 1);
} else {
int distance1 = EditDistance(a, a_offset + 1, b, b_offset) + 1;
int distance2 = EditDistance(a, a_offset, b, b_offset + 1) + 1;
int distance3 = EditDistance(a, a_offset + 1, b, b_offset + 1) + 1;
return Min(distance1, distance2, distance3);
}
}
}
int EditDistance_DP(const std::string& a, const std::string& b) {
int** cost = new int*[a.size() + 1];
for (int i = 0; i < a.size() + 1; ++i) {
cost[i] = new int[b.size() + 1];
}
for (int i = 0; i < a.size() + 1; ++i) {
for (int j = 0; j < b.size() + 1; ++j) {
cost[i][j] = 0;
}
}
for (int i = 0; i < a.size(); ++i) {
for (int j = 0; j < b.size(); ++j) {
if (a[i] == b[j]) {
cost[i + 1][j + 1] = Min(cost[i][j], cost[i][j + 1], cost[i + 1][j]);
} else {
cost[i + 1][j + 1] = Min(cost[i][j] + 1, cost[i][j + 1] + 1, cost[i + 1][j] + 1);
}
}
}
for (int i = 0; i <= a.size(); ++i) {
for (int j = 0; j <= b.size(); ++j) {
printf("%d ", cost[i][j]);
}
printf("\n");
}
int distance = cost[a.size()][b.size()];
for (int i = 0; i < a.size() + 1; ++i) {
delete[] cost[i];
}
delete[] cost;
return distance;
}
int main(int argc, char** argv) {
std::string a = "adefk";
std::string b = "bdefg";
printf("edit distance = %d\n", EditDistance(a, 0, b, 0));
printf("edit distance = %d\n", EditDistance_DP(a, b));
}
參考文獻:
編程之美 3.3