LoginSignup
0
1

More than 1 year has passed since last update.

Edit Distance【レーベンシュタイン距離を理解した】

Posted at

みなさま,お久しぶりです。学校がすごく忙しくてなかなか記事を書けていなくて申し訳ありません。レーベンシュタイン距離を理解したので覚書程度に書いていきます。

レーベンシュタイン距離とは?

「そもそもレーベンシュタイン距離とはなんぞや?」という人のために簡単に説明します。ある二つの文字列S,Tが与えられます。SをTにするには3つの手段があります。①一文字挿入(Insert)②一文字削除(Delete), ③一文字置換(Replace)です。これらを自由に用いて良い時の最小手順回数こそがレーベンシュタイン距離です。

解法とその意味

結論から言うと次のようなDP配列を考えます。
dp[i][j] : Sのi文字目までからTのj文字目までも部分文字列を作るときのレーベンシュタイン距離
あとはこれの漸化式を考えます。dp[i][j]を求めるとき, dp[i-1][j-1], dp[i-1][j], dp[i][j-1]は既知であるとすると次のような漸化式を立てることができます。

dp[i][j] = min(dp[i-1][j-1]+(s[i-1] == t[j-1] ? 0 : 1), dp[i-1][j]+1, dp[i][j-1]+1) 

それではこれが成り立つ理由について考えましょう。
①dp[i-1][j]からdp[i][j]への遷移
dp[i-1][j]は既知で, この値が示す意味はSのi-1文字目までからTのj文字目まで作るときの最小手数です。一方dp[i][j]はSのi文字目までからTのj文字目まで作るときの最小手数となります。
この2つの関係は明らかに, Sのi文字目までからTのj文字目まで作った文字列を一文字削除することで, Sのi-1文字目までからTのj文字目まで作った文字列を再現できる とわかります。よってdp[i][j] = min(dp[i][j], dp[i-1][j]+1)とできます。

②dp[i][j-1]からdp[i][j]への遷移
dp[i][j-1]は既知で, この値が示す意味はSのi文字目までからTのj-1文字目まで作るときの最小手数です。一方dp[i][j]はSのi文字目までからTのj文字目まで作るときの最小手数となります。
この2つの関係は明らかに, Sのi文字目までからTのj文字目まで作った文字列を一文字挿入することで, Sのi-1文字目までからTのj文字目まで作った文字列を再現できる とわかります。よってdp[i][j] = min(dp[i][j], dp[i][j-1]+1)とできます。

③dp[i-1][j-1]からdp[i][j]への遷移
dp[i-1][j-1]は既知で, この値が示す意味はSのi-1文字目までからTのj-1文字目まで作るときの最小手数です。一方dp[i][j]はSのi文字目までからTのj文字目まで作るときの最小手数となります。
この2つの関係は明らかに, Sのi文字目までからTのj文字目まで作った文字列を一文字置換することで, Sのi-1文字目までからTのj-1文字目まで作った文字列を再現できる とわかります。このとき, S[i-1] == T[j-1]であれば置換する必要はないです。よってdp[i][j] = min(dp[i][j], dp[i-1][j-1]+(S[i-1] == T[j-1] ? 0 : 1))とできます。

以上より次のようにコードを書くことができます。Verify先はAOJの以下の問題です。
問題のリンク:http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DPL_1_E&lang=jp

ソースコード

#include <bits/stdc++.h>
using namespace std;
using C = complex<double>;
const int mod = 998244353;
const long long LINF = 1001002003004005006;
const int INF = 1001001001;
const double PI = acos(-1);
const double EPS = 1e-10;
const int di[4] = {-1,0,1,0};
const int dj[4] = {0,-1,0,1};
const int dx[8] = {1,1,1,0,0,-1,-1,-1};
const int dy[8] = {1,0,-1,1,-1,1,0,-1};
# define sz(x) (int)(x).size()
# define rsz(x,n) x.resize(n)
# define yosupo(x) {cout << (x) << endl; return 0;}
# define ll long long
# define fi first
# define se second
# define pb push_back
# define pf push_front
# define eb emplace_back
# define ef emplace_front
# define pob pop_back
# define pof pop_front
# define GET_MACRO(_1, _2, _3, NAME, ...) NAME
# define _rep(i, n) _rep2(i, 0, n)
# define _rep2(i, a, b) for(int i = (int)(a); i < (int)(b); i++)
# define rep(...) GET_MACRO(__VA_ARGS__, _rep2, _rep)(__VA_ARGS__)
# define srep(i, a, b) for(int i = a; i <= b; ++i)
# define all(obj) (obj).begin(), (obj).end()
# define rall(obj) (obj).rbegin(), (obj).rend()
inline void YesNo(bool f) { std::cout << (f? "Yes": "No") << std::endl; }
void read() {}
template <typename T, class... U> void read(T &t, U &...u) { cin >> t; read(u...); }
void writeln() { cout << endl; }
template <typename T, class... U, char sep = ' '> void writeln(const T &t, const U &...u) { cout << t; if (sizeof...(u)) cout << sep; writeln(u...); }
# define Pll pair<ll, ll>
# define P pair<int,int>
# define bit(x,i) (((x) >> (i)) & 1)
# define equals(a, b) (fabs((a) - (b)) < EPS) // 誤差を考慮した同値判定
template<class T>bool chmax(T &a, const T &b) { if (a < b) { a = b; return 1; } return 0; }
template<class T>bool chmin(T &a, const T &b) { if (b < a) { a = b; return 1; } return 0; }
template<typename T>istream& operator>>(istream&i,vector<T>&v){rep(j,v.size())i>>v[j];return i;}
template<typename T>string join(vector<T>&v){stringstream s;rep(i,v.size())s<<' '<<v[i];return s.str().substr(1);}
template<typename T>ostream& operator<<(ostream&o,vector<T>&v){if(v.size())o<<join(v);return o;}
template<typename T>ostream& operator<<(ostream&o,vector<vector<T>>&vv){if(vv.size())o<<join(vv);return o;}
template <class T, class U> ostream& operator<<(ostream& os, const pair<T, U>& p) { return os << "P(" << p.first << " " << p.second << ")";}
template<typename T> using vc = vector<T>;
template<typename T> using vv = vc<vc<T>>;
using vi = vc<int>; using vvi = vv<int>;
using vl = vc<ll>; using vvl = vv<ll>;

int main() {
    string s,t;
    read(s,t);
    int n = sz(s), m = sz(t);
    int dp[n+10][m+10] = {};
    #define DELETE_COST 1
    #define INSERT_COST 1
    #define REPLACE_COST 1
    rep(i,n+1) dp[i][0] = INSERT_COST*i; 
    rep(j,m+1) dp[0][j] = INSERT_COST*j; 
    rep(i,1,n+1) {
        rep(j,1,m+1) {
            dp[i][j] = min({dp[i-1][j]+DELETE_COST,dp[i][j-1]+INSERT_COST,dp[i-1][j-1] + (s[i-1] == t[j-1] ? 0 : REPLACE_COST)});
        }
    }
    writeln(dp[n][m]);
}

以上です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1