計算字符串的相似度(編輯距離)

題目:

對於一個字符串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


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