レーベンシュタイン距離を完全に理解した!(C++編)
※この記事はQiitaからの移行記事です
こんにちは、競プロ絶賛勉強中のyapattaです。
AOJ(AIZU ONLINE JUDGE)のレーベンシュタイン距離の問題を解くことができなかったから、理解するために記事を書いた。プログラミングをやってない人でも理解できる記事を書きたい。
この記事を見て、レーベンシュタイン距離について理解して頂ければ幸いである。
最後に実装されたコードが書かれているので、コードだけみたい人は是非最後だけ見て欲しい。
1)レーベンシュタイン距離(編集距離)とは?
レーベンシュタイン距離とは、とりあえず2つの文字列がどのぐらい違っている文字列か表す指標であると理解してもらえるといい。
文字列Aの中の1文字を、置換、挿入、削除を繰り返しすことで、一方の文字列Aをもう一方の文字列Bに変形する。この繰り返しの最小回数をレーベンシュタイン距離という。詳しくは以下↓
2)どんな問題?
まずこんな問題
Edit Distance (Levenshtein Distance) | Aizu Online Judge
ある文字列を2つ入力して、その2つの文字列のレーベンシュタイン距離を出力するという問題
3)実装方法
レーベンシュタイン距離にある、以下の2つの性質を用いて漸化式を立てることで、レーベンシュタイン距離を求められる。
注)LP(x, y)
は文字列x
とy
のレーベンシュタイン距離を指す。
x
の長さがS
、文字列y
が空文字列(長さが0)のとき、LP(x, y) = S
性質1) 文字列x
の文字をS
回削除したら、x
が空文字列になるため、LP(x, y) = S
になることがわかると思う。
具体的に考えてみよう。 x = "apple"
(x
の長さは5)、y = ""
(y
の長さは0)とする。このとき、x
の文字を5回削除したら、x
はy
と等しくなるから確かに、LP(x, y) = 5
ちなみに、x
が空文字列、y
が長さS
の文字列であったとしても、x
にS
回文字を挿入したら、y
と同じ文字列になるため、LP(x, y) = S
LP(x,y)
は漸化式によってわかる
性質2) x = "book"
、y = "desk"
としたとき
(1)最後の文字が等しいときについて
LP("book", "desk") = LP("boo", "des")
これは、x[3]
(x
の4文字目)とy[3]
(y
の4文字目)が両方'k'
で等しいため、最後の文字'k'
がなくても編集距離は同じことによる
(2)削除について
LP("boo", "des") = LP("bo", "des") + 1
"boo"
から'o'
を削除したものが"bo"
となるから、確かに、
LP("boo", "des") = LP("bo", "des") + 1
が成り立つ
(3)挿入について
LP("boo", "des") = LP("boo", "de") + 1
"boo"
に一文字's'
を挿入したとき、LP("boo", "des") = LP("boos", "des") + 1
ここで(1)より、LP("boos", "des") = LP("boo", "de")
となるから、
確かに、LP("boo", "des") = LP("boo", "de") + 1
が成り立つ
(4)置換について
LP("boo", "des") = LP("bo", "de") + 1
"boo"
の3文字目の'o'
を's'
に置換したところ、LP("boo", "des") = LP("bos", "des") + 1
ここで(1)より、LP("bos", "des") = LP("bo", "de")
となるから、
確かに、LP("boo", "des") = LP("bo", "de") + 1
が成り立つ
(1)(2)(3)(4)の漸化式を用いて、最小のレーベンシュタイン距離を求めたいから、
配列LP
を確保して、j >= 1
かつ k >= 1
における任意のLP[j][k]
について、最後の文字が等しいかで場合分けして、
if xのj文字目 == yのj文字目
LP[j][k] = min(LP[j-1][k]+1, LP[j][k-1]+1, LP[j-1][k-1])
if xのj文字目 != yのj文字目
LP[j][k] = min(LP[j-1][k]+1, LP[j][k-1]+1, LP[j-1][k-1]+1)
としたら求まる
注) ここでLP[j][k]
はx
のj
文字目までの文字列と、y
のk
文字目までの文字列のレーベンシュタイン距離を表す。
じゃあ実際に実装してみよう
4)実装方法をもとにしてできたコード
#include <iostream>
#include <string>
using namespace std;
int LP[1005][1005]={};
int main() {
int j,k;
string x,y;
cin >> x >> y;
for(j=1;j<=x.size();j++) LP[j][0] = j;
for(k=1;k<=y.size();k++) LP[0][k] = k;
//aをbに近づけたい!
for(j=1;j<=x.size();j++) {
for(k=1;k<=y.size();k++) {
//a[j]を削除するか、a[j+1]にb[k]と同じ文字を挿入するか
//上記2つの行為の回数で最小な方を採用
int m = min(LP[j-1][k]+1, LP[j][k-1]+1);
if(x[j-1] == y[k-1]) {
//最後の文字が同じだから最後の文字がなくても編集距離は同じ
m = min(m,LP[j-1][k-1]);
LP[j][k] = m;
}else {
//最後の文字を置換する
m = min(m,LP[j-1][k-1]+1);
LP[j][k] = m;
}
}
}
cout << LP[x.size()][y.size()] << endl;
return 0;
}
Discussion